This Rmarkdown file assesses the output of CheckV, DeepVirFinder, Kaiju, VIBRANT, VirSorter, and VirSorter2 on multiple training sets of microbial DNA, primarily from NCBI. Created from fungal, viral, bacterial, archeael, protist, and plasmid DNA sequences

Please reach out to James Riddell () or Bridget Hegarty () regarding any issues, or open an issue on github.

library(ggplot2)
There were 30 warnings (use warnings() to see them)
library(plyr)
library(reshape2)
library(viridis)
library(tidyr)
library(dplyr)
library(readr)
library(data.table)
library(pROC)
Type 'citation("pROC")' for a citation.

Attaching package: ‘pROC’

The following objects are masked from ‘package:stats’:

    cov, smooth, var
library("stringr")

Import the file that combines the results from each of the tools from running “combining_tool_output.Rmd”:

viruses <- read_tsv("../IntermediaryFiles/viral_tools_combined.tsv")

── Column specification ─────────────────────────────────────────────────────────────────────────
cols(
  .default = col_double(),
  seqtype = col_character(),
  contig = col_character(),
  checkv_provirus = col_character(),
  checkv_quality = col_character(),
  method.x = col_character(),
  Classified = col_character(),
  IDs_all = col_character(),
  Seq = col_character(),
  Kaiju_Viral = col_character(),
  Kingdom = col_character(),
  type = col_character(),
  vibrant_quality = col_character(),
  method.y = col_character(),
  vibrant_prophage = col_character(),
  vs2type = col_character(),
  max_score_group = col_character(),
  provirus = col_logical()
)
ℹ Use `spec()` for the full column specifications.
There were 50 or more warnings (use warnings() to see the first 50)

This section defines a viralness score “keep_score” based on the tool classifications. A final keep_score above 1 indicates we will keep that sequence and call it viral.

VIBRANT Quality == “High Quality Draft”: +1 Quality == “Medium Quality Draft”: +1 Quality == “Low Quality Draft” & provirus: +0.5

Virsorter2 Viral >= 50: +0.5 Viral >= 0.95: +0.5 RNA >= 0.9: +1 lavidaviridae >= 0.9: +1 NCLDV >= 0.9: +1

Virsorter category == 1,4: +1 category == 2,5: +0.5

DeepVirFinder: Score >= 0.7: +0.5

Tuning - No Viral Signature: Kaiju_viral = “cellular organisms”: -0.5 If host_genes >50 and NOT provirus: -1 If viral_genes == 0 and host_genes >= 1: -1 If 3*viral_genes <= host_genes and NOT provirus: -1 If length > 50,000 and hallmark <=1: -1 If length < 5000 and checkv completeness <= 75: -0.5

Tuning - Viral Signature: Kaiju_viral = “Viruses”: +0.5 If %unknown >= 75 and length < 50000: +0.5 If %viral >= 50: +0.5 Hallmark > 2: +0.5

This script produces visualizations of these combined viral scorings and includes ecological metrics like alpha diversity.

You can decide which combination is appropriate for them and only need use the tools appropriate for your data.

getting_viral_set_1 <- function(input_seqs,
There were 50 or more warnings (use warnings() to see the first 50)
                                include_vibrant=FALSE, 
                                include_virsorter2=FALSE,
                                include_deepvirfinder=FALSE,
                                include_tuning_viral=FALSE,
                                include_tuning_not_viral=FALSE,
                                include_virsorter=FALSE) {
  
  keep_score <- rep(0, nrow(input_seqs))
  
  if (include_vibrant) {
    keep_score[input_seqs$vibrant_quality=="high quality draft"] <- keep_score[input_seqs$vibrant_quality=="high quality draft"] + 1
    keep_score[input_seqs$vibrant_quality=="medium quality draft"] <- keep_score[input_seqs$vibrant_quality=="medium quality draft"] + 1
    keep_score[input_seqs$vibrant_quality=="low quality draft" & input_seqs$provirus] <- keep_score[input_seqs$vibrant_quality=="low quality draft" & input_seqs$provirus] + 0.5
  }
  
  if (include_virsorter2) {
    #keep_score[input_seqs$viral>=50 | input_seqs$lavidaviridae>=0.95 | input_seqs$NCLDV>=0.95] <- keep_score[input_seqs$viral>=50 | input_seqs$lavidaviridae>=0.95 | input_seqs$NCLDV>=0.95] + 0.5
    keep_score[input_seqs$max_score>=50] <- keep_score[input_seqs$max_score>=50] + 0.5
    keep_score[input_seqs$max_score>=95] <- keep_score[input_seqs$max_score>=95] + 0.5
    #keep_score[input_seqs$RNA>=0.95] <- keep_score[input_seqs$RNA>=0.95] + 1
  }
  
  if (include_virsorter) {
    keep_score[input_seqs$category==1] <- keep_score[input_seqs$category==1] + 1
    keep_score[input_seqs$category==2] <- keep_score[input_seqs$category==2] + 0.5
    keep_score[input_seqs$category==4] <- keep_score[input_seqs$category==4] + 1
    keep_score[input_seqs$category==5] <- keep_score[input_seqs$category==5] + 0.5
  }
  
  if (include_deepvirfinder) {
    keep_score[input_seqs$score>=0.7 & input_seqs$checkv_length<20000] <- keep_score[input_seqs$score>=0.7 & input_seqs$checkv_length<20000] + 0.5
    keep_score[input_seqs$score>=0.9 & input_seqs$checkv_length<20000] <- keep_score[input_seqs$score>=0.9 & input_seqs$checkv_length<20000] + 0.5
  }
  
  if (include_tuning_viral) {
    keep_score[input_seqs$Kaiju_Viral=="Viruses"] <- keep_score[input_seqs$Kaiju_Viral=="Viruses"] + 0.5
    keep_score[input_seqs$hallmark>2] <- keep_score[input_seqs$hallmark>2] + 0.5
    keep_score[input_seqs$percent_unknown>=75 & input_seqs$checkv_length<50000] <- keep_score[input_seqs$percent_unknown>=75 & input_seqs$checkv_length<50000] + 0.5
    keep_score[input_seqs$viral>=50] <- keep_score[input_seqs$viral>=50] + 0.5
  }
  
  if (include_tuning_not_viral) {
    keep_score[input_seqs$Kaiju_Viral=="cellular organisms"] <- keep_score[input_seqs$Kaiju_Viral=="cellular organisms"] - 0.5
    keep_score[input_seqs$checkv_host_genes>50 & !input_seqs$provirus] <- keep_score[input_seqs$checkv_host_genes>50 & !input_seqs$provirus] - 1
    keep_score[input_seqs$checkv_viral_genes==0 & input_seqs$checkv_host_genes>=1] <- keep_score[input_seqs$checkv_viral_genes==0 & input_seqs$checkv_host_genes>=1] - 1
    keep_score[((input_seqs$checkv_viral_genes*3) <= input_seqs$checkv_host_genes) & !input_seqs$provirus] <- keep_score[((input_seqs$checkv_viral_genes*3) <= input_seqs$checkv_host_genes) & !input_seqs$provirus] - 1 
    keep_score[input_seqs$checkv_length>500000 & input_seqs$hallmark<=1] <- keep_score[input_seqs$checkv_length>500000 & input_seqs$hallmark<=1] - 1
    keep_score[(input_seqs$checkv_completeness<=75 | input_seqs$vibrant_quality=="complete circular")& input_seqs$checkv_length<=5000] <- keep_score[input_seqs$checkv_completeness<=75 & input_seqs$checkv_length<=5000] - 0.5 
  }
  
  return(keep_score)
  
}

Assessing performance against the “truth”

note that this is only as accurate as the annotations of the input sequences

this function calculates the precision, recall, and F1 score for each pipeline

assess_performance <- function(seqtype, keep_score) {
  
  truepositive <- rep("not viral", length(seqtype))
  truepositive[seqtype=="virus"] <- "viral"
  
  #make confusion matrix
  confusion_matrix <- rep("true negative", length(keep_score))
  confusion_matrix[truepositive=="viral" & keep_score<=1] <- "false negative"
  confusion_matrix[truepositive=="viral" & keep_score>=1] <- "true positive"
  confusion_matrix[truepositive=="not viral" & keep_score>=1] <- "false positive"
  
  TP <- table(confusion_matrix)[4]
  FP <- table(confusion_matrix)[2]
  TN <- table(confusion_matrix)[3]
  FN <- table(confusion_matrix)[1]
  
  precision <- TP/(TP+FP)
  recall <- TP/(TP+FN)
  F1 <- 2*precision*recall/(precision+recall)
  
  MCC <- (TP*TN-FP*FN)/sqrt(as.numeric(TP+FP)*as.numeric(TP+FN)*as.numeric(TN+FP)*as.numeric(TN+FN))
  
  auc <- round(auc(truepositive, keep_score),4)
  
  #by type metrics
  fungal_FP <- table(confusion_matrix[seqtype=="fungi"])[2]
  protist_FP <- table(confusion_matrix[seqtype=="protist"])[2]
  bacterial_FP <- table(confusion_matrix[seqtype=="bacteria"])[2]
  viral_FN <- table(confusion_matrix[seqtype=="virus"])[1]
  
  performance <- c(precision, recall, F1, MCC, auc, fungal_FP, 
                   protist_FP, bacterial_FP, viral_FN)
  names(performance) <- c("precision", "recall", "F1", "MCC", "AUC", "fungal_FP",
                          "protist_FP", "bacterial_FP", "viral_FN")
  
  return(performance)
}

combination of tools list

combos_list <- data.frame(toolcombo=rep(0, 64),
There were 27 warnings (use warnings() to see them)
                          tune_not_viral=rep(0, 64),
                          DVF=rep(0, 64),
                          tune_viral=rep(0, 64),
                          VIBRANT=rep(0, 64),
                          VS=rep(0, 64),
                          VS2=rep(0, 64))
p <- 1

for (i in c(0,1)){
  for (j in c(0,1)){
    for (k in c(0,1)){
      for (l in c(0,1)){
        for (m in c(0,1)){
          for (n in c(0,1)){
            combos_list$toolcombo[p] <- paste(i,j,k,l,m,n)
            combos_list$toolcombo2[p] <- paste(if(i){"tv"}else{"0"},if(j){"DVF"}else{"0"},
                                               if(k){"tnv"}else{"0"},if(l){"VB"}else{"0"},
                                               if(m){"VS"}else{"0"},if(n){"VS2"}else{"0"})
            combos_list$tune_not_viral[p] <- i
            combos_list$DVF[p] <- j
            combos_list$tune_viral[p] <- k
            combos_list$VIBRANT[p] <- l
            combos_list$VS[p] <- m
            combos_list$VS2[p] <- n
            p <- p+1
          }
        }
      }
    }
  }
}

combos_list <- combos_list[-1,]

this function builds a list of all of the combinations that the user wants to test. In this case, we’re comparing the performance of all unique combinations of the six tools.

build_score_list <- function(input_seqs, combos) {
  output <- data.frame(precision=rep(0, nrow(combos)),
                       recall=rep(0, nrow(combos)),
                       F1=rep(0, nrow(combos)),
                       MCC=rep(0, nrow(combos)),
                       AUC=rep(0, nrow(combos)),
                       fungal_FP=rep(0, nrow(combos)),
                       protist_FP=rep(0, nrow(combos)),
                       bacterial_FP=rep(0, nrow(combos)),
                       viral_FN=rep(0, nrow(combos)))
  for (i in 1:nrow(combos)) {
    keep_score <- getting_viral_set_1(input_seqs, include_vibrant = combos$VIBRANT[i],
                                            include_virsorter = combos$VS[i],
                                            include_virsorter2 = combos$VS2[i],
                                            include_tuning_viral = combos$tune_viral[i],
                                            include_tuning_not_viral = combos$tune_not_viral[i],
                                            include_deepvirfinder = combos$DVF[i])
  
    output[i,1:9] <- assess_performance(input_seqs$seqtype, keep_score)
    
    output$toolcombo[i] <- paste(combos$tune_viral[i],combos$DVF[i],
                                 combos$tune_not_viral[i], combos$VIBRANT[i],
                                 combos$VS[i], combos$VS2[i])
  }
  
  output[is.na(output)] <- 0

  return (output)
}

Calculate the performance of each pipeline

accuracy_scores <- data.frame(testing_set_index=rep(0, nrow(combos_list)*10),
                      precision=rep(0, nrow(combos_list)*10),
                       recall=rep(0, nrow(combos_list)*10),
                       F1=rep(0, nrow(combos_list)*10),
                       MCC=rep(0, nrow(combos_list)*10), 
                      AUC=rep(0, nrow(combos_list)*10),
                      fungal_FP=rep(0, nrow(combos_list)*10),
                      protist_FP=rep(0, nrow(combos_list)*10),
                      bacterial_FP=rep(0, nrow(combos_list)*10),
                      viral_FN=rep(0, nrow(combos_list)*10))

accuracy_scores <- cbind(testing_set_index=rep(1, nrow(combos_list)),
                              build_score_list(viruses[viruses$Index==1,], combos_list))
for (i in 2:10) {
  accuracy_scores <- rbind(accuracy_scores,
                           cbind(testing_set_index=rep(i, nrow(combos_list)),
                              build_score_list(viruses[viruses$Index==i,], combos_list)))
}
accuracy_scores$numrules <- str_count(accuracy_scores$toolcombo, "1")
There were 50 or more warnings (use warnings() to see the first 50)
#accuracy_scores <- accuracy_scores[order(accuracy_scores$numrules, decreasing=F),]
accuracy_scores <- accuracy_scores[order(accuracy_scores$MCC, decreasing=F),]
accuracy_scores$toolcombo <- factor(accuracy_scores$toolcombo, levels = unique(accuracy_scores$toolcombo))
accuracy_scores$numrules <- as.factor(accuracy_scores$numrules)
pal <- ggthemes::tableau_color_pal(palette="Tableau 10", type="regular")
There were 27 warnings (use warnings() to see them)

Visualize how the precision, recall, and F1 scores change across pipelines.

p2 <- ggplot(accuracy_scores, aes(x=toolcombo, y=F1, 
                                  color=numrules, fill=numrules)) +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Tool Combination (tv, DVF, tnv, VB, VS, VS2)") +
  ylab("F1 Score")
p2


ggplot(accuracy_scores, aes(x=toolcombo, y=precision, 
                                  color=numrules, fill=numrules)) +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Tool Combination (tv, DVF, tnv, VB, VS, VS2)") +
  ylab("Precision")

ggplot(accuracy_scores, aes(x=toolcombo, y=recall, 
                                  color=numrules, fill=numrules)) +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Tool Combination (tv, DVF, tnv, VB, VS, VS2)") +
  ylab("Recall")


ggplot(accuracy_scores, aes(x=precision, y=recall, 
                                  color=numrules, fill=numrules)) +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=20),
  ) +
  xlab("Precision") +
  ylab("Recall") +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(6)), 0.5)) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(6)), 1))


ggplot(accuracy_scores[accuracy_scores$testing_set_index==1,], aes(x=precision, y=recall)) +
  geom_label(alpha=0.7, label=accuracy_scores$toolcombo[accuracy_scores$testing_set_index==1]) +
  geom_point(alpha=0.5, aes(color=numrules, fill=numrules)) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Precision") +
  ylab("Recall")


ggplot(accuracy_scores, aes(x=toolcombo, y=abs(precision-recall), 
                                  color=numrules, fill=numrules)) +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Tool Combination (tv, DVF, tnv, VB, VS, VS2)") +
  ylab("Precision-Recall")

ggplot(accuracy_scores, aes(x=toolcombo, y=MCC, 
                                  color=numrules, fill=numrules)) +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=20),
  ) +
  xlab("Tool Combination (tv, DVF, tnv, VB, VS, VS2)") +
  ylab("MCC") +
  scale_fill_manual(name="Number of Rule Sets",
                     values = alpha(rev(pal(6)), 0.5)) +
  scale_color_manual(name="Number of Rule Sets",
                     values = alpha(rev(pal(6)), 1))

  
ggplot(accuracy_scores, aes(x=toolcombo, y=AUC, 
                                  color=numrules, fill=numrules)) +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Tool Combination (tv, DVF, tnv, VB, VS, VS2)") +
  ylab("AUC")

ggplot(accuracy_scores, aes(x=toolcombo, y=fungal_FP, 
                                  color=numrules, fill=numrules)) +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Tool Combination (tv, DVF, tnv, VB, VS, VS2)") +
  ylab("Fungal False Positives")


ggplot(accuracy_scores, aes(x=toolcombo, y=protist_FP, 
                                  color=numrules, fill=numrules)) +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Tool Combination (tv, DVF, tnv, VB, VS, VS2)") +
  ylab("Protist False Positives")


ggplot(accuracy_scores, aes(x=toolcombo, y=bacterial_FP, 
                                  color=numrules, fill=numrules)) +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Tool Combination (tv, DVF, tnv, VB, VS, VS2)") +
  ylab("Bacterial False Positives")


ggplot(accuracy_scores, aes(x=toolcombo, y=viral_FN, 
                                  color=numrules, fill=numrules)) +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Tool Combination (tv, DVF, tnv, VB, VS, VS2)") +
  ylab("Viral False Negatives")


ggplot(accuracy_scores, aes(x=toolcombo, y=viral_FN, 
                                  color=numrules, fill=numrules)) +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Tool Combination (tv, DVF, tnv, VB, VS, VS2)") +
  ylab("Viral False Negatives")


ggplot(accuracy_scores, aes(x=protist_FP, y=fungal_FP, 
                                  color=numrules, fill=numrules)) +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Protist FP") +
  ylab("Fungal FP")


ggplot(accuracy_scores, aes(x=recall, y=fungal_FP, 
                                  color=numrules, fill=numrules)) +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Recall") +
  ylab("Fungal FP")


ggplot(accuracy_scores, aes(x=recall, y=protist_FP, 
                                  color=numrules, fill=numrules)) +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Recall") +
  ylab("Protist FP")

accuracy_scores_melt <- accuracy_scores %>% 
There were 50 or more warnings (use warnings() to see the first 50)
  select(testing_set_index, precision, recall, MCC, numrules, toolcombo) %>%
  pivot_longer(cols=c(precision, recall, MCC), 
               names_to="performance_metric",
               values_to="performance_metric_score")
ggplot(accuracy_scores_melt[accuracy_scores_melt$performance_metric=="MCC",], aes(x=numrules, y=performance_metric_score, 
There were 30 warnings (use warnings() to see them)
                                  color=numrules, fill=numrules)) +
  geom_boxplot() +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  ylab("MCC") +
  xlab("Number of Rule Sets") +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(6)), 0.5)) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(6)), 1)) 

NA
ggplot(accuracy_scores_melt[accuracy_scores_melt$performance_metric=="recall",], aes(x=numrules, y=performance_metric_score, 
There were 30 warnings (use warnings() to see them)
                                  color=numrules, fill=numrules)) +
  geom_boxplot() +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  ylab("recall") +
  xlab("Number of Rule Sets") +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(6)), 0.5)) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(6)), 1)) 

NA
ggplot(accuracy_scores_melt[accuracy_scores_melt$performance_metric!="MCC",], aes(x=numrules, y=performance_metric_score, 
                                  color=numrules, fill=numrules)) +
  geom_boxplot() +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  ylab("Score") +
  xlab("Number of Rule Sets") +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(6)), 0.5)) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(6)), 1)) +
  facet_wrap(~performance_metric)

NA

comparing metric with and without tuning rules

accuracy_scores_melt$tuning_inc <- "no"
There were 50 or more warnings (use warnings() to see the first 50)
accuracy_scores_melt$tuning_inc[substring(accuracy_scores_melt$toolcombo, 1, 1)==1] <- "tv"
accuracy_scores_melt$tuning_inc[substring(accuracy_scores_melt$toolcombo, 3, 3)==1] <- "tnv"
accuracy_scores_melt$tuning_inc[substring(accuracy_scores_melt$toolcombo, 1, 1)==1 &
                                  substring(accuracy_scores_melt$toolcombo, 3, 3)==1] <- "both"
ggplot(accuracy_scores_melt, aes(x=tuning_inc, y=performance_metric_score)) +
  geom_boxplot() +
  geom_point(aes(color=numrules, fill=numrules), alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  ylab("MCC") +
  xlab("Number of Tools") +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(6)), 0.5)) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(6)), 1)) +
  facet_wrap(~performance_metric)


ggplot(accuracy_scores_melt, aes(x=tuning_inc, y=performance_metric_score,
                                 color=numrules, fill=numrules)) +
  geom_boxplot() +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  ylab("MCC") +
  xlab("Number of Tools") +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(6)), 0.5)) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(6)), 1)) +
  facet_wrap(~performance_metric)


ggplot(accuracy_scores_melt[accuracy_scores_melt$performance_metric!="MCC",], aes(x=tuning_inc, y=performance_metric_score)) +
  geom_boxplot() +
  geom_boxplot(aes(color=numrules, fill=numrules)) +
 # geom_point(aes(color=numrules, fill=numrules), alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  ylab("Score") +
  xlab("") +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(6)), 0.3)) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(6)), 0.7)) +
  facet_wrap(~performance_metric)

NA
write_tsv(accuracy_scores, "20221029_accuracy_scores.tsv")
There were 50 or more warnings (use warnings() to see the first 50)

to do: add in clustering and ordination like in the drinking water R notebook

Experimenting

high precision example

viruses$keep_score_high_precision <- getting_viral_set_1(viruses, include_deepvirfinder = T,
There were 20 warnings (use warnings() to see them)
                                              include_vibrant = T,
                                              include_virsorter2 = F,
                                              include_tuning_viral = F,
                                              include_tuning_not_viral = T,
                                              include_virsorter = F)
viruses$confusion_matrix_high_precision <- "true negative"
There were 25 warnings (use warnings() to see them)
viruses$confusion_matrix_high_precision[viruses$seqtype=="virus" & viruses$keep_score_high_precision<1] <- "false negative"
viruses$confusion_matrix_high_precision[viruses$seqtype=="virus" & viruses$keep_score_high_precision>=1] <- "true positive"
viruses$confusion_matrix_high_precision[viruses$seqtype!="virus" & viruses$keep_score_high_precision>=1] <- "false positive"

visualizing confusion matrix by taxa

confusion_by_taxa <- melt(table(viruses$confusion_matrix_high_precision, viruses$seqtype, viruses$Index))
The melt generic in data.table has been passed a table and will attempt to redirect to the relevant reshape2 method; please note that reshape2 is deprecated, and this redirection is now deprecated as well. To continue using melt methods from reshape2 while both libraries are attached, e.g. melt.list, you can prepend the namespace like reshape2::melt(table(viruses$confusion_matrix_high_precision, viruses$seqtype,     viruses$Index)). In the next version, this warning will become an error.
colnames(confusion_by_taxa) <- c("confusion_matrix", "seqtype","Index", "count")
length(grep("true", viruses$confusion_matrix_high_precision))/nrow(viruses)
[1] 0.9206364
pal <- ggthemes::tableau_color_pal(palette="Tableau 10", type="regular")
There were 44 warnings (use warnings() to see them)
ggplot(confusion_by_taxa, aes(y=count, x=confusion_matrix,
There were 44 warnings (use warnings() to see them)
                   fill=confusion_matrix,
                   color=confusion_matrix)) +
  geom_boxplot() +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(4)), 0.5),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(4)), 1),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  xlab("Number of Sequences") +
  ylab("") + 
  facet_wrap(~seqtype, scales = "free")

this rule set had the highest precision, but as you can see, this comes with a big sacrifice in recall

high MCC example

viruses$keep_score_high_MCC <- getting_viral_set_1(viruses, include_deepvirfinder = F,
There were 50 or more warnings (use warnings() to see the first 50)
                                              include_vibrant = T,
                                              include_virsorter2 = T,
                                              include_tuning_viral = T,
                                              include_tuning_not_viral = T,
                                              include_virsorter = T)
viruses$confusion_matrix_high_MCC <- "true negative"
viruses$confusion_matrix_high_MCC[viruses$seqtype=="virus" & viruses$keep_score_high_MCC<1] <- "false negative"
viruses$confusion_matrix_high_MCC[viruses$seqtype=="virus" & viruses$keep_score_high_MCC>=1] <- "true positive"
viruses$confusion_matrix_high_MCC[viruses$seqtype!="virus" & viruses$keep_score_high_MCC>=1] <- "false positive"

accuracy:

length(grep("true", viruses$confusion_matrix_high_MCC))/nrow(viruses)
[1] 0.935778

recall

length(grep("true positive", viruses$confusion_matrix_high_MCC))/length(grep("virus", viruses$seqtype))
[1] 0.7078
There were 24 warnings (use warnings() to see them)
TP <- table(viruses$confusion_matrix_high_MCC)[4]
FP <- table(viruses$confusion_matrix_high_MCC)[2]
TN <- table(viruses$confusion_matrix_high_MCC)[3]
FN <- table(viruses$confusion_matrix_high_MCC)[1]

precision <- as.numeric(TP/(TP+FP))
precision
[1] 0.6845261
recall <- as.numeric(TP/(TP+FN))
recall
[1] 0.7078
F1 <- as.numeric(2*precision*recall/(precision+recall))
F1
[1] 0.6959685
MCC <- as.numeric((TP*TN-FP*FN)/sqrt(as.numeric(TP+FP)*as.numeric(TP+FN)*as.numeric(TN+FP)*as.numeric(TN+FN)))
MCC
[1] 0.6601921

precision=69%, recall=87%, MCC=77%

precision adjusting size to be equal viral/not viral

TP <- table(viruses$confusion_matrix_high_MCC)[4]
There were 48 warnings (use warnings() to see them)
FP <- table(viruses$confusion_matrix_high_MCC)[2]*.11
TN <- table(viruses$confusion_matrix_high_MCC)[3]*.11
FN <- table(viruses$confusion_matrix_high_MCC)[1]

precision <- as.numeric(TP/(TP+FP))
precision
[1] 0.9561163
recall <- as.numeric(TP/(TP+FN))
recall
[1] 0.7523
F1 <- as.numeric(2*precision*recall/(precision+recall))
F1
[1] 0.8420504
MCC <- as.numeric((TP*TN-FP*FN)/sqrt(as.numeric(TP+FP)*as.numeric(TP+FN)*as.numeric(TN+FP)*as.numeric(TN+FN)))
MCC
[1] 0.7293445

precision=0.95, recall=0.87, F1=0.91, MCC=0.82

visualizing confusion matrix by taxa

confusion_by_taxa <- viruses %>% count(confusion_matrix_high_MCC, seqtype, Index)
There were 24 warnings (use warnings() to see them)
colnames(confusion_by_taxa) <- c("confusion_matrix", "seqtype","index", "count")
pal <- ggthemes::tableau_color_pal(palette="Tableau 10", type="regular")
ggplot(confusion_by_taxa, aes(x=count, y=as.factor(index),
There were 24 warnings (use warnings() to see them)
                   fill=confusion_matrix,
                   color=confusion_matrix)) +
  geom_bar(stat="identity") +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(4)), 0.5),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(4)), 1),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  xlab("Number of Sequences") +
  ylab("") + 
  facet_wrap(~seqtype, scales = "free") +
  coord_flip()

differences based on genome size

viruses$size_class <- "3-5kb"
viruses$size_class[viruses$checkv_length>5000] <- "5-10kb"
viruses$size_class[viruses$checkv_length>10000] <- ">10kb"
confusion_by_taxa <- viruses %>% count(confusion_matrix_high_MCC, seqtype, size_class, Index)

colnames(confusion_by_taxa) <- c("confusion_matrix", "seqtype","size", "index", "count")
confusion_vir_called <- confusion_by_taxa %>% filter(confusion_matrix=="true positive" | confusion_matrix=="false positive") 
There were 20 warnings (use warnings() to see them)
type_count <- viruses %>% count(seqtype, size_class, Index)

confusion_vir_called$per_viral <- 0

for (i in c(1:nrow(confusion_vir_called))) {
  confusion_vir_called$per_viral[i] <- confusion_vir_called$count[i]/type_count$n[type_count$seqtype==confusion_vir_called$seqtype[i] & 
                                                                                    type_count$Index==confusion_vir_called$index[i] &
                                                                                    type_count$size_class==confusion_vir_called$size[i]]*100
}

confusion_vir_called <- confusion_vir_called %>% group_by(seqtype, size) %>%
  summarise(mean=mean(per_viral), 
            sd=sd(per_viral))
`summarise()` has grouped output by 'seqtype'. You can override using the `.groups` argument.
confusion_vir_called$size <- factor(confusion_vir_called$size,
                                    levels = c("3-5kb", "5-10kb", ">10kb"))
ggplot(confusion_vir_called, aes(y=mean, x=size,
                   fill=seqtype,
                   color=seqtype)) +
  geom_bar(stat="identity", position=position_dodge()) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  geom_errorbar(aes(ymin=mean-sd, ymax=mean+sd), width=.2,
                 position=position_dodge(.9)) +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(6)), 0.5)) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(6)), 1)) +
  xlab("Length") +
  ylab("Sequences Called Viral (%)") 

viruses$keep_score_vb <- getting_viral_set_1(viruses, include_deepvirfinder = F,
There were 23 warnings (use warnings() to see them)
                                              include_vibrant = T,
                                              include_virsorter2 = F,
                                              include_tuning_viral = F,
                                              include_tuning_not_viral = F,
                                              include_virsorter = F)

viruses$keep_score_vb_dvf <- getting_viral_set_1(viruses, include_deepvirfinder = T,
                                              include_vibrant = T,
                                              include_virsorter2 = F,
                                              include_tuning_viral = F,
                                              include_tuning_not_viral = F,
                                              include_virsorter = F)

viruses$keep_score_vb_dvf_vs2 <- getting_viral_set_1(viruses, include_deepvirfinder = T,
                                              include_vibrant = T,
                                              include_virsorter2 = T,
                                              include_tuning_viral = F,
                                              include_tuning_not_viral = F,
                                              include_virsorter = F)

viruses$keep_score_vb_dvf_vs2_vs <- getting_viral_set_1(viruses, include_deepvirfinder = T,
                                              include_vibrant = T,
                                              include_virsorter2 = T,
                                              include_tuning_viral = F,
                                              include_tuning_not_viral = F,
                                              include_virsorter = T)

viruses$keep_score_vb_dvf_vs2_vs_tv <- getting_viral_set_1(viruses, include_deepvirfinder = T,
                                              include_vibrant = T,
                                              include_virsorter2 = T,
                                              include_tuning_viral = T,
                                              include_tuning_not_viral = F,
                                              include_virsorter = T)

viruses$keep_score_vb_dvf_vs2_vs_tv_tnv <- getting_viral_set_1(viruses, include_deepvirfinder = T,
                                              include_vibrant = T,
                                              include_virsorter2 = T,
                                              include_tuning_viral = T,
                                              include_tuning_not_viral = T,
                                              include_virsorter = T)
viruses$true_virus <- "not"
viruses$true_virus[viruses$seqtype=="virus"] <- "virus"

viruses_long_scores <- viruses %>% 
  select(contains("keep_score_vb"), size_class, true_virus) %>%
  pivot_longer(cols=contains("keep_score_"), 
               names_to="rule_combination",
               values_to="viral_score") %>% 
  mutate(viral_score=as.factor(round(viral_score))) %>%
  group_by(rule_combination, viral_score, size_class, true_virus) %>%
  summarise(n = n())
`summarise()` has grouped output by 'rule_combination', 'viral_score', 'size_class'. You can override using the `.groups` argument.
viruses_long_scores$size_class <- factor(viruses_long_scores$size_class,
                                    levels = c("3-5kb", "5-10kb", ">10kb"))
viruses_long_scores_addition <- viruses_long_scores[(viruses_long_scores$true_virus=="virus" &  (viruses_long_scores$rule_combination!="keep_score_vb_dvf_vs2_vs_tv_tnv") & viruses_long_scores$viral_score!="0"),]
There were 23 warnings (use warnings() to see them)
ggplot(viruses_long_scores_addition, aes(y=n, x=rule_combination,
                   fill=viral_score)) +
  geom_bar(stat="identity") +
  theme_light() +
  coord_flip() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    legend.position = "bottom"
  ) +
  scale_fill_brewer(palette = "Purples") +
  xlab("") +
  ylab("Number of Sequences") + 
  scale_x_discrete(labels=c("VB", "VB+DVF", "VB+DVF+VS2", "VB+DVF+VS2+VS",
                            "VB+DVF+VS2+VS+addition")) +
  facet_grid(~true_virus, scales = "free")

ggplot(viruses_long_scores, aes(y=n, x=rule_combination,
There were 50 or more warnings (use warnings() to see the first 50)
                   fill=viral_score)) +
  geom_bar(stat="identity") +
  theme_light() +
  coord_flip() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    legend.position = "bottom"
  ) +
  scale_fill_brewer(palette = "PuOr", ) +
  xlab("") +
  ylab("Number of Sequences") + 
  scale_x_discrete(labels=c("VB", "VB+DVF", "VB+DVF+VS2", "VB+DVF+VS2+VS",
                            "VB+DVF+VS2+VS+addition", "VB+DVF+VS2+VS+addition-removal")) +
  facet_grid(~true_virus, scales = "free")

ggplot(viruses_long_scores, aes(y=n, x=rule_combination,
There were 20 warnings (use warnings() to see them)
                   fill=viral_score)) +
  geom_bar(stat="identity") +
  theme_light() +
  coord_flip() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    legend.position = "bottom"
  ) +
  scale_fill_brewer(palette = "PuOr", ) +
  xlab("") +
  ylab("Number of Sequences") + 
  facet_grid(size_class~true_virus, scales = "free")

Considering how each method contributes to the final prediction

viruses_high <- viruses[viruses$keep_score_vb_dvf_vs2_vs_tv>=1,] 
There were 50 or more warnings (use warnings() to see the first 50)
viruses_high_mod <- viruses_high %>% select(keep_score_vb,keep_score_vb_dvf, 
                                            keep_score_vb_dvf_vs2, keep_score_vb_dvf_vs2_vs, 
                                            keep_score_vb_dvf_vs2_vs_tv, keep_score_vb_dvf_vs2_vs_tv_tnv)
#viruses_high_mod <- apply(viruses_high_mod, c(1,2), function(x) {if (x >= 1) {x <- 1} else {x <- 0}})
viruses_high_mod <- as_tibble(viruses_high_mod)
sm_m <- reshape2::melt(viruses_high_mod)
No id variables; using all as measure variables
colnames(sm_m) <- c("method", "viral_score")
sm_m <- sm_m[sm_m$viral_score>0,]

sm_m$score <- sm_m$viral_score

sm_m$score[sm_m$viral_score==0.5] <- "0.5"
sm_m$score[sm_m$viral_score>=1] <- "1"
sm_m$score[sm_m$viral_score>=2] <- "2"
sm_m$score[sm_m$viral_score>=3] <- "3"
sm_m$score[sm_m$viral_score>=4] <- "4"
sm_m$score[sm_m$viral_score>=5] <- "5"

sm_m$score <- factor(sm_m$score, 
                                       levels=c("0.5", "1", "2","3","4","5"))
ggplot(sm_m, aes(x=method, y=score,
                   fill=score)) +
  geom_bar(stat="identity") +
  theme_light() +
  coord_flip() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    legend.position = "bottom"
  ) +
  scale_fill_manual(name = 'Viral Score',
                     values = alpha(c(viridis(6)), 1)) +
  xlab("") +
  ylab("Number of Sequences") +
  coord_flip()
Coordinate system already present. Adding new coordinate system, which will replace the existing one.

Another way of visualizing between rule sets

viruses_mcc_alluvial <- data.frame(seqtype=viruses$seqtype,
                                   keep_score_high_MCC=viruses$keep_score_high_MCC,
                                   confusion_matrix_high_MCC=viruses$confusion_matrix_high_MCC)


viruses_mcc_alluvial$keep_score_vb <- getting_viral_set_1(viruses, include_deepvirfinder = F,
                                              include_vibrant = T,
                                              include_virsorter2 = F,
                                              include_tuning_viral = F,
                                              include_tuning_not_viral = F,
                                              include_virsorter = F)

viruses_mcc_alluvial$keep_score_dvf <- getting_viral_set_1(viruses, include_deepvirfinder = F,
                                              include_vibrant = T,
                                              include_virsorter2 = F,
                                              include_tuning_viral = F,
                                              include_tuning_not_viral = F,
                                              include_virsorter = F)

viruses_mcc_alluvial$keep_score_vs <- getting_viral_set_1(viruses, include_deepvirfinder = F,
                                              include_vibrant = F,
                                              include_virsorter2 = F,
                                              include_tuning_viral = F,
                                              include_tuning_not_viral = F,
                                              include_virsorter = T)

viruses_mcc_alluvial$keep_score_vs2 <- getting_viral_set_1(viruses, include_deepvirfinder = F,
                                              include_vibrant = F,
                                              include_virsorter2 = T,
                                              include_tuning_viral = F,
                                              include_tuning_not_viral = F,
                                              include_virsorter = F)

viruses_mcc_alluvial$keep_score_tv <- getting_viral_set_1(viruses, include_deepvirfinder = F,
                                              include_vibrant = F,
                                              include_virsorter2 = F,
                                              include_tuning_viral = T,
                                              include_tuning_not_viral = F,
                                              include_virsorter = F)

viruses_mcc_alluvial$keep_score_tnv <- getting_viral_set_1(viruses, include_deepvirfinder = F,
                                              include_vibrant = T,
                                              include_virsorter2 = F,
                                              include_tuning_viral = F,
                                              include_tuning_not_viral = T,
                                              include_virsorter = F)
viruses_mcc_alluvial %>%
  count(seqtype, keep_score_high_MCC) %>% 
  spread(key = keep_score_high_MCC, value=n)
viruses_mcc_alluvial <- viruses_mcc_alluvial %>%
  count(seqtype, keep_score_dvf, keep_score_vb, keep_score_vs,
        keep_score_vs2, keep_score_tv, keep_score_tnv, keep_score_high_MCC) %>%
  mutate(high_mcc_viral_score=factor(round(keep_score_high_MCC)))
ggplot(viruses_mcc_alluvial,
       aes(axis1 = keep_score_dvf, axis2 = keep_score_vb, 
           axis3 = keep_score_vs, axis4 = keep_score_vs2, 
           axis5 = keep_score_tv, axis6 = keep_score_tnv, 
           y=n)) +
  geom_alluvium(aes(fill=high_mcc_viral_score),
                width = 0, knot.pos = 0, reverse = FALSE) +
  geom_stratum(width = 1/5) +
  theme_bw() +
  geom_text(stat = "stratum", aes(label = after_stat(stratum)),
            reverse = FALSE) +
  theme(
        axis.text.x=element_text(size=14, angle = 90)
        ) +
  scale_x_continuous(breaks=c(1,2,3,4,5,6),
    labels=c("dvf", "kj", "vs", "vs2",
             "tv", "tnv")) +
  scale_fill_brewer(palette = "PuOr", ) +
  facet_wrap(~seqtype, scales="free_y") 

Visualizing confusion matrix by number of tools

viruses$keep_score_visualize <- viruses$keep_score_high_MCC
viruses$keep_score_visualize[viruses$keep_score_high_MCC>1] <- "> 1"
viruses$keep_score_visualize[viruses$keep_score_high_MCC==1] <- "1"
viruses$keep_score_visualize[viruses$keep_score_high_MCC==0.5] <- "0.5"
viruses$keep_score_visualize[viruses$keep_score_high_MCC==0] <- "0"
viruses$keep_score_visualize[viruses$keep_score_high_MCC==-0.5] <- "-0.5"
viruses$keep_score_visualize[viruses$keep_score_high_MCC==-1] <- "-1"
viruses$keep_score_visualize[viruses$keep_score_high_MCC<=-1] <- "< -1"

viruses$keep_score_visualize <- factor(viruses$keep_score_visualize, 
                                       levels=c("< -1", "-1", "-0.5", "0", "0.5","1", "> 1"))
#viruses$keep_score_visualize <- factor(viruses$keep_score_visualize, 
#                                       labels=c("≤ 0", "≤ 0", "≤ 0", "0.5","1", "> 1"))
levels(factor(viruses$keep_score_visualize))
ggplot(viruses, aes(x=as.factor(Index),
                   fill=keep_score_visualize, color=keep_score_visualize)) +
  geom_bar(stat="count", position="stack") +
  theme_light() +
  coord_flip() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16)
  ) +
  scale_color_manual(name = 'Viral Score',
                     values = alpha(c(viridis(6)), 1)) +
  scale_fill_manual(name = 'Viral Score',
                     values = alpha(c(viridis(6)), 0.5)) +
  xlab("Index") +
  ylab("Sequence Count") +
  facet_wrap(~confusion_matrix_high_MCC, scales = "free")

clustering

viral_scores <- matrix(data=0, nrow=nrow(viruses), ncol=nrow(combos_list))
num_viruses <- data.frame(toolcombo=rep(0, nrow(combos_list)),
                          num_viruses=rep(0, nrow(combos_list)))

for (i in 1:nrow(combos_list)) {
  viral_scores[,i] <- getting_viral_set_1(viruses, include_vibrant = combos_list$VIBRANT[i],
                                            include_virsorter = combos_list$VS[i],
                                            include_virsorter2 = combos_list$VS2[i],
                                            include_tuning_viral = combos_list$tune_viral[i],
                                            include_tuning_not_viral = combos_list$tune_not_viral[i],
                                            include_deepvirfinder = combos_list$DVF[i])
  
  if (max(viral_scores[,i])<=0) {
    num_viruses$num_viruses[i] <- 0
  }
  else {
    num_viruses$num_viruses[i] <- table(viral_scores[,i]>=1)[[2]]
  }
  
  num_viruses$toolcombo[i] <- combos_list$toolcombo[i]
  
  num_viruses$toolcombo2[i] <- combos_list$toolcombo2[i]
}

num_viruses$numrules <- str_count(num_viruses$toolcombo, "1")
num_viruses <- num_viruses[order(num_viruses$num_viruses, decreasing=F),]
num_viruses$toolcombo <- factor(num_viruses$toolcombo, levels = unique(num_viruses$toolcombo))
num_viruses$toolcombo2 <- factor(num_viruses$toolcombo2, levels = unique(num_viruses$toolcombo2))
num_viruses$numrules <- as.factor(num_viruses$numrules)
viral_scores_nozeros <- viral_scores[rowSums(viral_scores)>0,]
viral_scores_nozeros <- viral_scores_nozeros + 1
viral_scores_nozeros <- as.data.frame(viral_scores_nozeros)

colnames(viral_scores_nozeros) <- num_viruses$toolcombo
library(phyloseq)
tooldata <- num_viruses

rownames(tooldata) <- tooldata$toolcombo
physeq_pooled <- phyloseq(otu_table(viral_scores_nozeros, taxa_are_rows = T),
                                     sample_data(tooldata))
ordination <- phyloseq::ordinate(physeq =physeq_pooled, method = "PCoA", distance = "bray")
phyloseq::plot_ordination(physeq = physeq_pooled, ordination = ordination,
                          shape="numrules", color="num_viruses") + 
  geom_point(size = 3) +
  theme_bw() +
  geom_label(label=tooldata$toolcombo)

phyloseq::plot_ordination(physeq = physeq_pooled, ordination = ordination,
                          shape="numrules", color="num_viruses") + 
  geom_point(size = 3) +
  theme_bw()
bray_dist <- phyloseq::distance(physeq_pooled, method="bray")
clusters <- hclust(dist(bray_dist))
plot(clusters)

myclusters <- cutree(clusters, h=0.8)
#names(myclusters[myclusters==1])
#names(myclusters[myclusters==2])
#names(myclusters[myclusters==3])
#names(myclusters[myclusters==4])
#names(myclusters[myclusters==5])

myclusters_df <- tibble(combo=names(myclusters),
                            cluster_index=myclusters)

myclusters_df <- separate(myclusters_df, col=combo, into=c("tnv", "DVF",
                                                            "tv", "VB",
                                                            "VS", "VS2"),
                          sep=" ", remove = F)


tool_count <- as.data.frame(rbind(table(myclusters_df$tnv, myclusters_df$cluster_index)[2,],
                          table(myclusters_df$DVF, myclusters_df$cluster_index)[2,],
                          table(myclusters_df$tv, myclusters_df$cluster_index)[2,],
                          table(myclusters_df$VB, myclusters_df$cluster_index)[2,],
                          table(myclusters_df$VS, myclusters_df$cluster_index)[2,],
                          table(myclusters_df$VS2, myclusters_df$cluster_index)[2,])
                    )

tool_count <- data.frame(t(apply(tool_count, c(1), function(x) {x <- x/table(myclusters_df$cluster_index)})))



tool_count$method <- c("tnv", "DVF", "tv", "VB", "VS", "VS2")

tool_count <- melt(tool_count)

colnames(tool_count) <- c("tool", "cluster_index", "tool_count_norm")
pal <- ggthemes::tableau_color_pal(palette="Tableau 10", type="regular")

ggplot(tool_count, aes(x=tool, y=tool_count_norm,
                   fill=tool,
                   color=tool)) +
  geom_bar(stat="identity") +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "none",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    #legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(6)), 0.5)) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(6)), 1)) +
  xlab("Tool") +
  ylab("Proportion of Times in Cluster") + 
  facet_wrap(~cluster_index, nrow=1)
accuracy_scores_melt <- accuracy_scores %>% 
  select(precision, recall, MCC, numrules, toolcombo) %>%
  group_by(numrules, toolcombo) %>%
  summarise(precision=mean(precision),
            recall=mean(recall),
            MCC=mean(MCC)) %>%
  pivot_longer(cols=c(precision, recall, MCC), 
               names_to="performance_metric",
               values_to="performance_metric_score")
myclusters_df <- inner_join(accuracy_scores_melt, myclusters_df, 
                            by=c("toolcombo"="combo"))

myclusters_df$cluster_index <- as.factor(myclusters_df$cluster_index)
ggplot(myclusters_df, aes(x=cluster_index, y=performance_metric_score, 
                                  color=cluster_index, fill=cluster_index)) +
  geom_boxplot() +
  geom_point(alpha=0.5) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  ylab("MCC") +
  xlab("Cluster") +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(9)), 0.5)) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(9)), 1)) +
  facet_wrap(~performance_metric)

all 6 tools example

viruses$keep_score_all <- getting_viral_set_1(viruses, include_deepvirfinder = T,
                                              include_vibrant = T,
                                              include_virsorter2 = T,
                                              include_tuning_viral = T,
                                              include_tuning_not_viral = T,
                                              include_virsorter = T)
viruses$confusion_matrix_all <- "true negative"
viruses$confusion_matrix_all[viruses$seqtype=="virus" & viruses$keep_score_all<1] <- "false negative"
viruses$confusion_matrix_all[viruses$seqtype=="virus" & viruses$keep_score_all>=1] <- "true positive"
viruses$confusion_matrix_all[viruses$seqtype!="virus" & viruses$keep_score_all>=1] <- "false positive"
TP <- table(viruses$confusion_matrix_all)[4]
FP <- table(viruses$confusion_matrix_all)[2]
TN <- table(viruses$confusion_matrix_all)[3]
FN <- table(viruses$confusion_matrix_all)[1]

precision <- TP/(TP+FP)
precision
recall <- TP/(TP+FN)
recall
F1 <- 2*precision*recall/(precision+recall)
F1

MCC <- (TP*TN-FP*FN)/sqrt(as.numeric(TP+FP)*as.numeric(TP+FN)*as.numeric(TN+FP)*as.numeric(TN+FN))
MCC

precision=62%, recall=92%, MCC=73%

visualizing confusion matrix by taxa

confusion_by_taxa <- melt(table(viruses$confusion_matrix_all, viruses$seqtype, viruses$Index))
colnames(confusion_by_taxa) <- c("confusion_matrix", "seqtype","Index", "count")
table(viruses$confusion_matrix_all)

length(grep("true", viruses$confusion_matrix_all))/nrow(viruses)
length(grep("true positive", viruses$confusion_matrix_all))/length(grep("virus", viruses$seqtype))
pal <- ggthemes::tableau_color_pal(palette="Tableau 10", type="regular")
ggplot(confusion_by_taxa, aes(x=count, y=as.factor(Index),
                   fill=confusion_matrix,
                   color=confusion_matrix)) +
  geom_bar(stat="identity") +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(4)), 0.5),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(4)), 1),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  xlab("Number of Sequences") +
  ylab("") + 
  facet_wrap(~seqtype, scales = "free") +
  coord_flip()

high recall example

viruses$keep_score_high_recall <- getting_viral_set_1(viruses, include_deepvirfinder = T,
                                              include_vibrant = T,
                                              include_virsorter2 = T,
                                              include_tuning_viral = T,
                                              include_tuning_not_viral = F,
                                              include_virsorter = T)
viruses$confusion_matrix_high_recall <- "true negative"
viruses$confusion_matrix_high_recall[viruses$seqtype=="virus" & viruses$keep_score_high_recall<1] <- "false negative"
viruses$confusion_matrix_high_recall[viruses$seqtype=="virus" & viruses$keep_score_high_recall>=1] <- "true positive"
viruses$confusion_matrix_high_recall[viruses$seqtype!="virus" & viruses$keep_score_high_recall>=1] <- "false positive"

visualizing confusion matrix by taxa

confusion_by_taxa <- melt(table(viruses$confusion_matrix_high_recall, viruses$seqtype, viruses$Index))
colnames(confusion_by_taxa) <- c("confusion_matrix", "seqtype","Index", "count")
pal <- ggthemes::tableau_color_pal(palette="Tableau 10", type="regular")
p2 <- ggplot(confusion_by_taxa, aes(x=count, y=as.factor(Index),
                   fill=confusion_matrix,
                   color=confusion_matrix)) +
  geom_bar(stat="identity") +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(4)), 0.5),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(4)), 1),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  xlab("Number of Sequences") +
  ylab("") + 
  facet_wrap(~seqtype, scales = "free") +
  coord_flip()
p2

accuracy:

length(grep("true", viruses$confusion_matrix_high_recall))/nrow(viruses)

0.887

recall

length(grep("true positive", viruses$confusion_matrix_high_recall))/length(grep("virus", viruses$seqtype))

recover almost all of the viruses this way, but more protist contamination

0.960

confusion_by_taxa <- viruses %>% count(confusion_matrix_high_recall, seqtype, size_class)

colnames(confusion_by_taxa) <- c("confusion_matrix", "seqtype","size", "count")

few tools, high MCC example

viruses$keep_score_few_tools <- getting_viral_set_1(viruses, include_deepvirfinder = F,
There were 50 or more warnings (use warnings() to see the first 50)
                                              include_vibrant = F,
                                              include_virsorter2 = T,
                                              include_tuning_viral = T,
                                              include_tuning_not_viral = T,
                                              include_virsorter = F)
viruses$confusion_matrix_few_tools <- "true negative"
viruses$confusion_matrix_few_tools[viruses$seqtype=="virus" & viruses$keep_score_few_tools<1] <- "false negative"
viruses$confusion_matrix_few_tools[viruses$seqtype=="virus" & viruses$keep_score_few_tools>=1] <- "true positive"
viruses$confusion_matrix_few_tools[viruses$seqtype!="virus" & viruses$keep_score_few_tools>=1] <- "false positive"
TP <- table(viruses$confusion_matrix_few_tools)[4]
FP <- table(viruses$confusion_matrix_few_tools)[2]
TN <- table(viruses$confusion_matrix_few_tools)[3]
FN <- table(viruses$confusion_matrix_few_tools)[1]

precision <- TP/(TP+FP)
precision
recall <- TP/(TP+FN)
recall
F1 <- 2*precision*recall/(precision+recall)
F1

MCC <- (TP*TN-FP*FN)/sqrt(as.numeric(TP+FP)*as.numeric(TP+FN)*as.numeric(TN+FP)*as.numeric(TN+FN))
MCC

precision=77%, recall=76%, MCC=74%

Visualizing the different sets

confusion_by_taxa_method <- viruses %>% 
  select(contains("confusion_matrix"), seqtype, Index) %>%
  pivot_longer(cols=contains("confusion_matrix"), 
               names_to="confusion_matrix_type",
               values_to="confusion_matrix_value") %>%
  count(seqtype, Index, confusion_matrix_type, confusion_matrix_value)
pal <- ggthemes::tableau_color_pal(palette="Tableau 10", type="regular")
ggplot(confusion_by_taxa_method, aes(y=n, x=confusion_matrix_type,
                   fill=confusion_matrix_value,
                   color=confusion_matrix_value)) +
  geom_boxplot() +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(4)), 0.5),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(4)), 1),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  xlab("Number of Sequences") +
  ylab("") + 
  facet_wrap(~seqtype, scales = "free")
confusion_by_taxa_method <- viruses %>% 
  select(contains("confusion_matrix"), seqtype, Index) %>%
  pivot_longer(cols=contains("confusion_matrix"), 
               names_to="confusion_matrix_type",
               values_to="confusion_matrix_value") %>%
  count(seqtype, Index, confusion_matrix_type, confusion_matrix_value) %>%
  filter(grepl("true", confusion_matrix_value)) %>%
  mutate(confusion_matrix_type=sub("confusion_matrix_", "", confusion_matrix_type))
ggplot(confusion_by_taxa_method, aes(y=n, x=confusion_matrix_type,
                   fill=confusion_matrix_value,
                   color=confusion_matrix_value)) +
  geom_boxplot() +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=10, angle=90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(4))[3:4], 0.5),
                    labels=c( 
                             "true negative", "true positive")) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(4))[3:4], 1),
                    labels=c(
                             "true negative", "true positive")) +
  xlab("Tool Set") +
  ylab("Contig Count") + 
  facet_wrap(~seqtype, scales = "free")

another way of visualizing the different tool sets scores

viruses$true_virus <- "not"
There were 40 warnings (use warnings() to see them)
viruses$true_virus[viruses$seqtype=="virus"] <- "virus"

viruses_long_scores <- viruses %>% 
  select(contains("keep_score_high"), contains("keep_score_all"), size_class, true_virus) %>%
  pivot_longer(cols=contains("keep_score_"), 
               names_to="rule_combination",
               values_to="viral_score") %>% 
  mutate(viral_score=as.factor(round(viral_score))) %>%
  group_by(rule_combination, viral_score, size_class, true_virus) %>%
  summarise(n = n())
`summarise()` has grouped output by 'rule_combination', 'viral_score', 'size_class'. You can override using the `.groups` argument.
viruses_long_scores$size_class <- factor(viruses_long_scores$size_class,
                                    levels = c("3-5kb", "5-10kb", ">10kb"))
ggplot(viruses_long_scores, aes(y=n, x=rule_combination,
There were 20 warnings (use warnings() to see them)
                   fill=viral_score)) +
  geom_bar(stat="identity") +
  theme_light() +
  coord_flip() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    legend.position = "bottom"
  ) +
  scale_fill_brewer(palette = "PuOr", ) +
  xlab("") +
  ylab("Number of Sequences") + 
  facet_grid(~true_virus, scales = "free")

ggplot(viruses_long_scores, aes(y=n, x=rule_combination,
There were 20 warnings (use warnings() to see them)
                   fill=viral_score)) +
  geom_bar(stat="identity") +
  theme_light() +
  coord_flip() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    legend.position = "bottom"
  ) +
  scale_fill_brewer(palette = "PuOr", ) +
  xlab("") +
  ylab("Number of Sequences") + 
  facet_grid(size_class~true_virus, scales = "free")

Extra Stuff #####################################################################

ggplot(viruses, aes(x=checkv_length, y=keep_score_high_MCC,
                   fill=confusion_matrix_high_MCC,
                   color=confusion_matrix_high_MCC)) +
  geom_point(stat="identity", shape=21) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(4)), 0.5),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(4)), 1),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  xlab("Sequence Length (bp)") +
  ylab("Pipeline Viral Score") + 
  facet_wrap(~seqtype) + 
  scale_x_log10()
ggplot(viruses, aes(x=checkv_completeness, y=hallmark,
                   fill=confusion_matrix_high_recall,
                   color=confusion_matrix_high_recall)) +
  geom_point(stat="identity", shape=21) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(4)), 0.5),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(4)), 1),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  xlab("CheckV Completeness") +
  ylab("Number of Hallmark Genes") + 
  facet_wrap(~seqtype) + 
  scale_x_log10()
ggplot(viruses, aes(x=checkv_completeness, y=keep_score_high_MCC,
                   fill=confusion_matrix_high_recall,
                   color=confusion_matrix_high_recall)) +
  geom_point(stat="identity", shape=21) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(4)), 0.5),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(4)), 1),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  xlab("CheckV Completeness") +
  ylab("Pipeline Viral Score") + 
  facet_wrap(~seqtype) + 
  scale_x_log10()
ggplot(viruses, aes(x=confusion_matrix_high_recall, y=checkv_length,
                   fill=confusion_matrix_high_recall,
                   color=confusion_matrix_high_recall)) +
  geom_boxplot() +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(4)), 0.5),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(4)), 1),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  xlab("Sequence Length (bp)") +
  ylab("Pipeline Viral Score") +
  scale_y_log10()

looking at false negatives

viruses_false_negs <- viruses[(viruses$seqtype=="virus" & viruses$keep_score_high_recall<1),]

looking at protists calling viral

viruses_false_pos_protists <- viruses[(viruses$seqtype=="protist" & viruses$keep_score_high_recall>=1),]

Considering how each method contributes to the final prediction (high MCC)

viruses$keep_score_vb <- getting_viral_set_1(viruses, include_deepvirfinder = F,
                                              include_vibrant = T,
                                              include_virsorter2 = F,
                                              include_tuning_viral = F,
                                              include_tuning_not_viral = F,
                                              include_virsorter = F)

viruses$keep_score_vb_tv <- getting_viral_set_1(viruses, include_deepvirfinder = F,
                                              include_vibrant = T,
                                              include_virsorter2 = T,
                                              include_tuning_viral = T,
                                              include_tuning_not_viral = F,
                                              include_virsorter = F)
viruses_high <- viruses[viruses$keep_score_vb_tv>=1,] #uncomment this line if want to use all 6 tools
viruses_high_mod <- viruses_high %>% select(keep_score_vb, 
                                            keep_score_vb_tv)
#viruses_high_mod <- apply(viruses_high_mod, c(1,2), function(x) {if (x >= 1) {x <- 1} else {x <- 0}})
viruses_high_mod <- as_tibble(viruses_high_mod)
sm_m <- reshape2::melt(viruses_high_mod)
colnames(sm_m) <- c("method", "score")
ggplot(sm_m, aes(x=method, y=score,
                   fill=as.factor(score))) +
  geom_bar(stat="identity") +
  theme_light() +
  coord_flip() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom"
  ) +
  scale_fill_manual(name = 'Number of Methods',
                     values = alpha(c(viridis(14)), 1)) +
  xlab("Primary Method") +
  ylab("Count of Viral Contigs") +
  coord_flip()

ROC

library(pROC)
viruses$truepositive <- rep(0, nrow(viruses))
viruses$truepositive[viruses$seqtype=="virus"] <- 1
rocobj <- roc(viruses$truepositive, viruses$keep_score)
rocobj_all <- roc(viruses$truepositive, viruses$keep_score_all)
auc <- round(auc(viruses$truepositive, viruses$keep_score),4)
auc_all <- round(auc(viruses$truepositive, viruses$keep_score_all),4)
#create ROC plot
ggroc(rocobj, colour = 'steelblue', size = 2) +
  ggtitle(paste0('ROC Curve ', '(AUC = ', auc, ')')) +
  coord_equal()
ggroc(rocobj_all, colour = 'green', size = 2) +
  ggtitle(paste0('ROC Curve ', '(AUC = ', auc_all, ')'))

Sensitivity: The probability that the model predicts a positive outcome for an observation when indeed the outcome is positive. Specificity: The probability that the model predicts a negative outcome for an observation when indeed the outcome is negative.

Comparing behavior of all testing sets combined (clustering analyses)

viral_scores <- matrix(data=0, nrow=nrow(viruses), ncol=nrow(combos_list))
num_viruses <- data.frame(toolcombo=rep(0, nrow(combos_list)),
                          num_viruses=rep(0, nrow(combos_list)))

for (i in 1:nrow(combos_list)) {
  viral_scores[,i] <- getting_viral_set_1(viruses, include_vibrant = combos_list$VIBRANT[i],
                                            include_virsorter = combos_list$VS[i],
                                            include_virsorter2 = combos_list$VS2[i],
                                            include_tuning = combos_list$CheckV[i],
                                            include_kaiju = combos_list$Kaiju[i],
                                            include_deepvirfinder = combos_list$DVF[i])
  
  num_viruses$num_viruses[i] <- table(viral_scores[,i]>=1)[[2]]
  
  num_viruses$toolcombo[i] <- combos_list$toolcombo[i]
  
  num_viruses$toolcombo2[i] <- combos_list$toolcombo2[i]
}

num_viruses$numrules <- str_count(num_viruses$toolcombo, "1")
num_viruses <- num_viruses[order(num_viruses$num_viruses, decreasing=F),]
num_viruses$toolcombo <- factor(num_viruses$toolcombo, levels = unique(num_viruses$toolcombo))
num_viruses$toolcombo2 <- factor(num_viruses$toolcombo2, levels = unique(num_viruses$toolcombo2))
num_viruses$numrules <- as.factor(num_viruses$numrules)
ggplot(num_viruses, aes(x=toolcombo, y=num_viruses, 
                                  color=numrules, fill=numrules)) +
  geom_point() +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Tool Combination (CV, DVF, KJ, VB, VS, VS2)") +
  ylab("Num Viruses Predicted")

ggplot(num_viruses, aes(x=toolcombo2, y=num_viruses, 
                                  color=numrules, fill=numrules)) +
  geom_point() +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Tool Combination (CV, DVF, KJ, VB, VS, VS2)") +
  ylab("Num Viruses Predicted")
ggplot(num_viruses, aes(x=numrules, y=num_viruses)) +
  geom_boxplot(aes(color=numrules)) +
  geom_point(aes(color=numrules, fill=numrules)) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14, angle = 90),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Number of Tools") +
  ylab("Num Viruses Predicted")
viral_scores_nozeros <- viral_scores[rowSums(viral_scores)>0,]
viral_scores_nozeros <- viral_scores_nozeros + 1
viral_scores_nozeros <- as.data.frame(viral_scores_nozeros)

colnames(viral_scores_nozeros) <- num_viruses$toolcombo2
library(phyloseq)
tooldata <- num_viruses

rownames(tooldata) <- tooldata$toolcombo2
physeq_pooled <- phyloseq(otu_table(viral_scores_nozeros, taxa_are_rows = T),
                                     sample_data(tooldata))
ordination <- phyloseq::ordinate(physeq =physeq_pooled, method = "PCoA", distance = "bray")
phyloseq::plot_ordination(physeq = physeq_pooled, ordination = ordination,
                          shape="numrules", color="num_viruses") + 
  geom_point(size = 3) +
  theme_bw() +
  geom_label(label=tooldata$toolcombo)

phyloseq::plot_ordination(physeq = physeq_pooled, ordination = ordination,
                          shape="numrules", color="num_viruses") + 
  geom_point(size = 3) +
  theme_bw()

to do: try coloring above based on the F1 scores of the testing set on each combination

bray_dist <- phyloseq::distance(physeq_pooled, method="bray")
clusters <- hclust(dist(bray_dist))
plot(clusters)

myclusters <- cutree(clusters, h=1.1)
names(myclusters[myclusters==1])
names(myclusters[myclusters==2])
names(myclusters[myclusters==3])
names(myclusters[myclusters==4])
names(myclusters[myclusters==5])

myclusters_df <- tibble(combo=names(myclusters),
                            cluster_index=myclusters)

myclusters_df <- separate(myclusters_df, col=combo, into=c("CheckV", "DVF",
                                                            "Kaiju", "VIBRANT",
                                                            "VirSorter", "VirSorter2"),
                          sep=" ", remove = F)


tool_count <- as.data.frame(rbind(table(myclusters_df$CheckV, myclusters_df$cluster_index)[2,],
                          table(myclusters_df$DVF, myclusters_df$cluster_index)[2,],
                          table(myclusters_df$Kaiju, myclusters_df$cluster_index)[2,],
                          table(myclusters_df$VIBRANT, myclusters_df$cluster_index)[2,],
                          table(myclusters_df$VirSorter, myclusters_df$cluster_index)[2,],
                          table(myclusters_df$VirSorter2, myclusters_df$cluster_index)[2,])
                    )

tool_count$method <- c("CheckV", "DVF", "Kaiju", "VIBRANT", "VirSorter", "VirSorter2")

tool_count <- melt(tool_count)

colnames(tool_count) <- c("tool", "cluster_index", "tool_count")
pal <- ggthemes::tableau_color_pal(palette="Tableau 10", type="regular")

ggplot(tool_count, aes(x=cluster_index, y=tool_count,
                   fill=cluster_index,
                   color=cluster_index)) +
  geom_bar(stat="identity") +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(6)), 0.5)) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(6)), 1)) +
  xlab("Cluster") +
  ylab("Number of Times in Cluster") + 
  facet_wrap(~tool, scales = "free")
 ggplot(viruses, aes(x=checkv_viral_genes, y=confusion_matrix_high_precision,
                   fill=confusion_matrix_high_precision,
                   color=confusion_matrix_high_precision)) +
  geom_boxplot(alpha=0.3) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(4)), 0.5),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(4)), 1),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  xlab("Number of Viral Sequences") +
  ylab("") + 
  facet_wrap(~seqtype, scales = "free") +
  coord_flip()

 ggplot(viruses, aes(x=percent_viral, y=confusion_matrix_high_precision,
                   fill=confusion_matrix_high_precision,
                   color=confusion_matrix_high_precision)) +
  geom_boxplot(alpha=0.3) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(4)), 0.5),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(4)), 1),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  xlab("Percent Genes Viral") +
  ylab("") + 
  facet_wrap(~seqtype, scales = "free") +
  coord_flip()

 ggplot(viruses, aes(x=hallmark, y=confusion_matrix_high_precision,
                   fill=confusion_matrix_high_precision,
                   color=confusion_matrix_high_precision)) +
  geom_boxplot(alpha=0.3) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(4)), 0.5),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(4)), 1),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  xlab("Number of Hallmark Genes") +
  ylab("") + 
  facet_wrap(~seqtype, scales = "free") +
  coord_flip()
 
ggplot(viruses, aes(x=hallmark, y=checkv_viral_genes,
                   fill=confusion_matrix_high_precision,
                   color=confusion_matrix_high_precision)) +
  geom_point(alpha=0.3) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  scale_fill_manual(name="",
                     values = alpha(rev(pal(4)), 0.5),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  scale_color_manual(name="",
                     values = alpha(rev(pal(4)), 1),
                    labels=c("false negative", "false positive", 
                             "true negative", "true positive")) +
  xlab("Number of Hallmark Genes") +
  ylab("Number of Viral Genes") + 
  facet_wrap(~seqtype, scales = "free") +
  coord_flip()
viruses_false_positive <- viruses[viruses$confusion_matrix_high_precision=="false positive",]
viruses_false_negative <- viruses[viruses$confusion_matrix_high_precision=="false negative",]
ggplot(viruses, aes(x=hallmark, y=checkv_viral_genes,
                   fill=checkv_length,
                   color=checkv_length,
                   shape=checkv_provirus)) +
  geom_point(alpha=0.3) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Number of Hallmark Genes") +
  ylab("Number of Viral Genes") + 
  facet_wrap(~seqtype, scales = "free") +
  coord_flip()

ggplot(viruses_false_positive, aes(x=hallmark, y=checkv_length,
                   fill=checkv_viral_genes,
                   color=checkv_viral_genes,
                   shape=checkv_provirus)) +
  geom_point(alpha=0.3) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Number of Hallmark Genes") +
  ylab("Contig Length") + 
  facet_wrap(~seqtype, scales = "free") +
  coord_flip()

ggplot(viruses_false_positive[viruses_false_positive$seqtype=="bacteria"], aes(x=hallmark, y=checkv_length,
                   fill=checkv_viral_genes,
                   color=checkv_viral_genes,
                   shape=checkv_provirus)) +
  geom_point(alpha=0.3) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Number of Hallmark Genes") +
  ylab("Contig Length") + 
  facet_wrap(~Kaiju_Viral, scales = "free") +
  coord_flip()

ggplot(viruses_false_positive[viruses_false_positive$seqtype=="fungi"], aes(x=hallmark, y=checkv_length,
                   fill=keep_score_high_precision,
                   color=keep_score_high_precision,
                   shape=checkv_provirus)) +
  geom_point(alpha=0.3) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Number of Hallmark Genes") +
  ylab("Contig Length") + 
  facet_wrap(~Kaiju_Viral, scales = "free") +
  coord_flip()

ggplot(viruses_false_positive[viruses_false_positive$seqtype=="protist"], aes(x=hallmark, y=checkv_length,
                   fill=checkv_viral_genes,
                   color=checkv_viral_genes,
                   shape=checkv_provirus)) +
  geom_point(alpha=0.3) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Number of Hallmark Genes") +
  ylab("Contig Length") + 
  facet_wrap(~Kaiju_Viral, scales = "free") +
  coord_flip()

ggplot(viruses_false_negative, aes(x=hallmark, y=checkv_length,
                   fill=checkv_viral_genes,
                   color=checkv_viral_genes,
                   shape=checkv_provirus)) +
  geom_point(alpha=0.3) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Number of Hallmark Genes") +
  ylab("Contig Length") + 
  facet_wrap(~Kaiju_Viral, scales = "free") +
  coord_flip()

ggplot(viruses_false_negative, aes(x=hallmark, y=checkv_length,
                   fill=keep_score_high_precision,
                   color=keep_score_high_precision,
                   shape=checkv_provirus)) +
  geom_point(alpha=0.3) +
  theme_light() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16),
  ) +
  xlab("Number of Hallmark Genes") +
  ylab("Contig Length") + 
  facet_wrap(~Kaiju_Viral, scales = "free") +
  coord_flip()
table(viruses$hallmark[viruses$confusion_matrix_high_precision=="false positive"]>0)

table(viruses$percent_host[viruses$confusion_matrix_high_precision=="false positive"]<50)

Visualizing confusion matrix by number of tools

confusion_by_keep_score <- viruses %>% 
  select(contains("keep_score"), seqtype, Index) %>%
  pivot_longer(cols=contains("keep_score"), 
               names_to="pipeline_type",
               values_to="pipeline_value") %>%
  mutate(pipeline_type=sub("keep_score_", "", pipeline_type)) %>%
  count(seqtype, Index, pipeline_type, pipeline_value)
confusion_by_keep_score$confusion_matrix <- "true negative"
confusion_by_keep_score$confusion_matrix[confusion_by_keep_score$seqtype=="virus" & confusion_by_keep_score$pipeline_value<1] <- "false negative"
confusion_by_keep_score$confusion_matrix[confusion_by_keep_score$seqtype=="virus" & confusion_by_keep_score$pipeline_value>=1] <- "true positive"
confusion_by_keep_score$confusion_matrix[confusion_by_keep_score$seqtype!="virus" & confusion_by_keep_score$pipeline_value>=1] <- "false positive"
confusion_by_keep_score$keep_score_visualize <- confusion_by_keep_score$pipeline_value
confusion_by_keep_score$keep_score_visualize[confusion_by_keep_score$pipeline_value>1] <- "> 1"
confusion_by_keep_score$keep_score_visualize[confusion_by_keep_score$pipeline_value==1] <- "1"
confusion_by_keep_score$keep_score_visualize[confusion_by_keep_score$pipeline_value==0.5] <- "0.5"
confusion_by_keep_score$keep_score_visualize[confusion_by_keep_score$pipeline_value==0] <- "0"
confusion_by_keep_score$keep_score_visualize[confusion_by_keep_score$pipeline_value==-0.5] <- "-0.5"
confusion_by_keep_score$keep_score_visualize[confusion_by_keep_score$pipeline_value==-1] <- "-1"
confusion_by_keep_score$keep_score_visualize[confusion_by_keep_score$pipeline_value<=-1] <- "< -1"

confusion_by_keep_score$keep_score_visualize <- factor(confusion_by_keep_score$keep_score_visualize, 
                                       levels=c("< -1", "-1", "-0.5", "0", "0.5","1", "> 1"))
ggplot(confusion_by_keep_score, aes(x=confusion_matrix, y=n,
                   fill=keep_score_visualize, color=keep_score_visualize)) +
  geom_boxplot() +
  theme_light() +
  coord_flip() +
  theme(
    panel.grid.major.y = element_blank(),
    panel.border = element_blank(),
    axis.ticks.y = element_blank(),
    legend.position = "bottom",
    axis.text.y=element_text(size=14),
    axis.text.x=element_text(size=14),
    legend.text=element_text(size=12),
    axis.title=element_text(size=16)
  ) +
  scale_color_manual(name = 'Viral Score',
                     values = alpha(c(viridis(6)), 1)) +
  scale_fill_manual(name = 'Viral Score',
                     values = alpha(c(viridis(6)), 0.5)) +
  xlab("Index") +
  ylab("Sequence Count") +
  facet_wrap(~pipeline_type, scales = "free")
LS0tCnRpdGxlOiAiVmlyYWwgU2VxdWVuY2UgU29ydGluZyBUb29scyBFdmFsdWF0aW9uIgphdXRob3I6IEJyaWRnZXQgSGVnYXJ0eSwgSmFtZXMgUmlkZGVsbApkYXRlOiAwNy0yMi0yMDIyCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KVGhpcyBSbWFya2Rvd24gZmlsZSBhc3Nlc3NlcyB0aGUgb3V0cHV0IG9mIENoZWNrViwgRGVlcFZpckZpbmRlciwgS2FpanUsClZJQlJBTlQsIFZpclNvcnRlciwgYW5kIFZpclNvcnRlcjIgb24gbXVsdGlwbGUgdHJhaW5pbmcgc2V0cyBvZiBtaWNyb2JpYWwgRE5BLCAKcHJpbWFyaWx5IGZyb20gTkNCSS4gQ3JlYXRlZCBmcm9tIGZ1bmdhbCwgdmlyYWwsIGJhY3RlcmlhbCwgYXJjaGVhZWwsIHByb3Rpc3QsCmFuZCBwbGFzbWlkIEROQSBzZXF1ZW5jZXMKClBsZWFzZSByZWFjaCBvdXQgdG8gSmFtZXMgUmlkZGVsbCAocmlkZGVsbC4yNkBidWNrZXllbWFpbC5vc3UuZWR1KSBvcgpCcmlkZ2V0IEhlZ2FydHkgKGJlaDUzQGNhc2UuZWR1KSByZWdhcmRpbmcgYW55IGlzc3Vlcywgb3Igb3BlbiBhbiBpc3N1ZSBvbiBnaXRodWIuCgpgYGB7ciBzZXR1cC1saWJyYXJ5fQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkocGx5cikKbGlicmFyeShyZXNoYXBlMikKbGlicmFyeSh2aXJpZGlzKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KGRhdGEudGFibGUpCmxpYnJhcnkocFJPQykKbGlicmFyeSgic3RyaW5nciIpCmBgYAoKSW1wb3J0IHRoZSBmaWxlIHRoYXQgY29tYmluZXMgdGhlIHJlc3VsdHMgZnJvbSBlYWNoIG9mIHRoZSB0b29scyBmcm9tIHJ1bm5pbmcgImNvbWJpbmluZ190b29sX291dHB1dC5SbWQiOgpgYGB7cn0KdmlydXNlcyA8LSByZWFkX3RzdigiLi4vSW50ZXJtZWRpYXJ5RmlsZXMvdmlyYWxfdG9vbHNfY29tYmluZWQudHN2IikKYGBgCgpUaGlzIHNlY3Rpb24gZGVmaW5lcyBhIHZpcmFsbmVzcyBzY29yZSAia2VlcF9zY29yZSIgYmFzZWQgb24gdGhlIHRvb2wgY2xhc3NpZmljYXRpb25zLiAKQSBmaW5hbCBrZWVwX3Njb3JlIGFib3ZlIDEgaW5kaWNhdGVzIHdlIHdpbGwga2VlcCB0aGF0IHNlcXVlbmNlIGFuZCBjYWxsIGl0IHZpcmFsLgoKVklCUkFOVAogICAgUXVhbGl0eSA9PSAiSGlnaCBRdWFsaXR5IERyYWZ0IjogKzEKICAgIFF1YWxpdHkgPT0gIk1lZGl1bSBRdWFsaXR5IERyYWZ0IjogKzEKICAgIFF1YWxpdHkgPT0gIkxvdyBRdWFsaXR5IERyYWZ0IiAmIHByb3ZpcnVzOiArMC41CgpWaXJzb3J0ZXIyCiAgICBWaXJhbCA+PSA1MDogKzAuNQogICAgVmlyYWwgPj0gMC45NTogKzAuNQogICAgUk5BID49IDAuOTogKzEKICAgIGxhdmlkYXZpcmlkYWUgPj0gMC45OiArMQogICAgTkNMRFYgPj0gMC45OiArMQoKVmlyc29ydGVyCiAgICBjYXRlZ29yeSA9PSAgMSw0OiArMQogICAgY2F0ZWdvcnkgPT0gMiw1OiArMC41CgpEZWVwVmlyRmluZGVyOgogICAgU2NvcmUgPj0gMC43OiArMC41CgpUdW5pbmcgLSBObyBWaXJhbCBTaWduYXR1cmU6CiAgICBLYWlqdV92aXJhbCA9ICJjZWxsdWxhciBvcmdhbmlzbXMiOiAtMC41CiAgICBJZiBob3N0X2dlbmVzID41MCBhbmQgTk9UIHByb3ZpcnVzOiAtMSAKICAgIElmIHZpcmFsX2dlbmVzID09IDAgYW5kIGhvc3RfZ2VuZXMgPj0gMTogLTEKICAgIElmIDMqdmlyYWxfZ2VuZXMgPD0gaG9zdF9nZW5lcyBhbmQgTk9UIHByb3ZpcnVzOiAtMQogICAgSWYgbGVuZ3RoID4gNTAsMDAwIGFuZCBoYWxsbWFyayA8PTE6IC0xCiAgICBJZiBsZW5ndGggPCA1MDAwIGFuZCBjaGVja3YgY29tcGxldGVuZXNzIDw9IDc1OiAtMC41CgpUdW5pbmcgLSBWaXJhbCBTaWduYXR1cmU6CiAgICBLYWlqdV92aXJhbCA9ICJWaXJ1c2VzIjogKzAuNQogICAgSWYgJXVua25vd24gPj0gNzUgYW5kIGxlbmd0aCA8IDUwMDAwOiArMC41CiAgICBJZiAldmlyYWwgPj0gNTA6ICswLjUKICAgIEhhbGxtYXJrID4gMjogKzAuNQogICAgCgpUaGlzIHNjcmlwdCBwcm9kdWNlcyB2aXN1YWxpemF0aW9ucyBvZiB0aGVzZSBjb21iaW5lZCB2aXJhbCBzY29yaW5ncyBhbmQKaW5jbHVkZXMgZWNvbG9naWNhbCBtZXRyaWNzIGxpa2UgYWxwaGEgZGl2ZXJzaXR5LgoKWW91IGNhbiBkZWNpZGUgd2hpY2ggY29tYmluYXRpb24gaXMgYXBwcm9wcmlhdGUgZm9yIHRoZW0gYW5kIG9ubHkgbmVlZCB1c2UgdGhlCnRvb2xzIGFwcHJvcHJpYXRlIGZvciB5b3VyIGRhdGEuCgpgYGB7ciBnZXR0aW5nX3ZpcmFsX3NldF8xfQpnZXR0aW5nX3ZpcmFsX3NldF8xIDwtIGZ1bmN0aW9uKGlucHV0X3NlcXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aWJyYW50PUZBTFNFLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlcjI9RkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV9kZWVwdmlyZmluZGVyPUZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX3ZpcmFsPUZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX25vdF92aXJhbD1GQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlcj1GQUxTRSkgewogIAogIGtlZXBfc2NvcmUgPC0gcmVwKDAsIG5yb3coaW5wdXRfc2VxcykpCiAgCiAgaWYgKGluY2x1ZGVfdmlicmFudCkgewogICAga2VlcF9zY29yZVtpbnB1dF9zZXFzJHZpYnJhbnRfcXVhbGl0eT09ImhpZ2ggcXVhbGl0eSBkcmFmdCJdIDwtIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyR2aWJyYW50X3F1YWxpdHk9PSJoaWdoIHF1YWxpdHkgZHJhZnQiXSArIDEKICAgIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyR2aWJyYW50X3F1YWxpdHk9PSJtZWRpdW0gcXVhbGl0eSBkcmFmdCJdIDwtIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyR2aWJyYW50X3F1YWxpdHk9PSJtZWRpdW0gcXVhbGl0eSBkcmFmdCJdICsgMQogICAga2VlcF9zY29yZVtpbnB1dF9zZXFzJHZpYnJhbnRfcXVhbGl0eT09ImxvdyBxdWFsaXR5IGRyYWZ0IiAmIGlucHV0X3NlcXMkcHJvdmlydXNdIDwtIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyR2aWJyYW50X3F1YWxpdHk9PSJsb3cgcXVhbGl0eSBkcmFmdCIgJiBpbnB1dF9zZXFzJHByb3ZpcnVzXSArIDAuNQogIH0KICAKICBpZiAoaW5jbHVkZV92aXJzb3J0ZXIyKSB7CiAgICAja2VlcF9zY29yZVtpbnB1dF9zZXFzJHZpcmFsPj01MCB8IGlucHV0X3NlcXMkbGF2aWRhdmlyaWRhZT49MC45NSB8IGlucHV0X3NlcXMkTkNMRFY+PTAuOTVdIDwtIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyR2aXJhbD49NTAgfCBpbnB1dF9zZXFzJGxhdmlkYXZpcmlkYWU+PTAuOTUgfCBpbnB1dF9zZXFzJE5DTERWPj0wLjk1XSArIDAuNQogICAga2VlcF9zY29yZVtpbnB1dF9zZXFzJG1heF9zY29yZT49NTBdIDwtIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRtYXhfc2NvcmU+PTUwXSArIDAuNQogICAga2VlcF9zY29yZVtpbnB1dF9zZXFzJG1heF9zY29yZT49OTVdIDwtIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRtYXhfc2NvcmU+PTk1XSArIDAuNQogICAgI2tlZXBfc2NvcmVbaW5wdXRfc2VxcyRSTkE+PTAuOTVdIDwtIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRSTkE+PTAuOTVdICsgMQogIH0KICAKICBpZiAoaW5jbHVkZV92aXJzb3J0ZXIpIHsKICAgIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRjYXRlZ29yeT09MV0gPC0ga2VlcF9zY29yZVtpbnB1dF9zZXFzJGNhdGVnb3J5PT0xXSArIDEKICAgIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRjYXRlZ29yeT09Ml0gPC0ga2VlcF9zY29yZVtpbnB1dF9zZXFzJGNhdGVnb3J5PT0yXSArIDAuNQogICAga2VlcF9zY29yZVtpbnB1dF9zZXFzJGNhdGVnb3J5PT00XSA8LSBrZWVwX3Njb3JlW2lucHV0X3NlcXMkY2F0ZWdvcnk9PTRdICsgMQogICAga2VlcF9zY29yZVtpbnB1dF9zZXFzJGNhdGVnb3J5PT01XSA8LSBrZWVwX3Njb3JlW2lucHV0X3NlcXMkY2F0ZWdvcnk9PTVdICsgMC41CiAgfQogIAogIGlmIChpbmNsdWRlX2RlZXB2aXJmaW5kZXIpIHsKICAgIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRzY29yZT49MC43ICYgaW5wdXRfc2VxcyRjaGVja3ZfbGVuZ3RoPDIwMDAwXSA8LSBrZWVwX3Njb3JlW2lucHV0X3NlcXMkc2NvcmU+PTAuNyAmIGlucHV0X3NlcXMkY2hlY2t2X2xlbmd0aDwyMDAwMF0gKyAwLjUKICAgIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRzY29yZT49MC45ICYgaW5wdXRfc2VxcyRjaGVja3ZfbGVuZ3RoPDIwMDAwXSA8LSBrZWVwX3Njb3JlW2lucHV0X3NlcXMkc2NvcmU+PTAuOSAmIGlucHV0X3NlcXMkY2hlY2t2X2xlbmd0aDwyMDAwMF0gKyAwLjUKICB9CiAgCiAgaWYgKGluY2x1ZGVfdHVuaW5nX3ZpcmFsKSB7CiAgICBrZWVwX3Njb3JlW2lucHV0X3NlcXMkS2FpanVfVmlyYWw9PSJWaXJ1c2VzIl0gPC0ga2VlcF9zY29yZVtpbnB1dF9zZXFzJEthaWp1X1ZpcmFsPT0iVmlydXNlcyJdICsgMC41CiAgICBrZWVwX3Njb3JlW2lucHV0X3NlcXMkaGFsbG1hcms+Ml0gPC0ga2VlcF9zY29yZVtpbnB1dF9zZXFzJGhhbGxtYXJrPjJdICsgMC41CiAgICBrZWVwX3Njb3JlW2lucHV0X3NlcXMkcGVyY2VudF91bmtub3duPj03NSAmIGlucHV0X3NlcXMkY2hlY2t2X2xlbmd0aDw1MDAwMF0gPC0ga2VlcF9zY29yZVtpbnB1dF9zZXFzJHBlcmNlbnRfdW5rbm93bj49NzUgJiBpbnB1dF9zZXFzJGNoZWNrdl9sZW5ndGg8NTAwMDBdICsgMC41CiAgICBrZWVwX3Njb3JlW2lucHV0X3NlcXMkdmlyYWw+PTUwXSA8LSBrZWVwX3Njb3JlW2lucHV0X3NlcXMkdmlyYWw+PTUwXSArIDAuNQogIH0KICAKICBpZiAoaW5jbHVkZV90dW5pbmdfbm90X3ZpcmFsKSB7CiAgICBrZWVwX3Njb3JlW2lucHV0X3NlcXMkS2FpanVfVmlyYWw9PSJjZWxsdWxhciBvcmdhbmlzbXMiXSA8LSBrZWVwX3Njb3JlW2lucHV0X3NlcXMkS2FpanVfVmlyYWw9PSJjZWxsdWxhciBvcmdhbmlzbXMiXSAtIDAuNQogICAga2VlcF9zY29yZVtpbnB1dF9zZXFzJGNoZWNrdl9ob3N0X2dlbmVzPjUwICYgIWlucHV0X3NlcXMkcHJvdmlydXNdIDwtIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRjaGVja3ZfaG9zdF9nZW5lcz41MCAmICFpbnB1dF9zZXFzJHByb3ZpcnVzXSAtIDEKICAgIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRjaGVja3ZfdmlyYWxfZ2VuZXM9PTAgJiBpbnB1dF9zZXFzJGNoZWNrdl9ob3N0X2dlbmVzPj0xXSA8LSBrZWVwX3Njb3JlW2lucHV0X3NlcXMkY2hlY2t2X3ZpcmFsX2dlbmVzPT0wICYgaW5wdXRfc2VxcyRjaGVja3ZfaG9zdF9nZW5lcz49MV0gLSAxCiAgICBrZWVwX3Njb3JlWygoaW5wdXRfc2VxcyRjaGVja3ZfdmlyYWxfZ2VuZXMqMykgPD0gaW5wdXRfc2VxcyRjaGVja3ZfaG9zdF9nZW5lcykgJiAhaW5wdXRfc2VxcyRwcm92aXJ1c10gPC0ga2VlcF9zY29yZVsoKGlucHV0X3NlcXMkY2hlY2t2X3ZpcmFsX2dlbmVzKjMpIDw9IGlucHV0X3NlcXMkY2hlY2t2X2hvc3RfZ2VuZXMpICYgIWlucHV0X3NlcXMkcHJvdmlydXNdIC0gMSAKICAgIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRjaGVja3ZfbGVuZ3RoPjUwMDAwMCAmIGlucHV0X3NlcXMkaGFsbG1hcms8PTFdIDwtIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRjaGVja3ZfbGVuZ3RoPjUwMDAwMCAmIGlucHV0X3NlcXMkaGFsbG1hcms8PTFdIC0gMQogICAga2VlcF9zY29yZVsoaW5wdXRfc2VxcyRjaGVja3ZfY29tcGxldGVuZXNzPD03NSB8IGlucHV0X3NlcXMkdmlicmFudF9xdWFsaXR5PT0iY29tcGxldGUgY2lyY3VsYXIiKSYgaW5wdXRfc2VxcyRjaGVja3ZfbGVuZ3RoPD01MDAwXSA8LSBrZWVwX3Njb3JlW2lucHV0X3NlcXMkY2hlY2t2X2NvbXBsZXRlbmVzczw9NzUgJiBpbnB1dF9zZXFzJGNoZWNrdl9sZW5ndGg8PTUwMDBdIC0gMC41IAogIH0KICAKICByZXR1cm4oa2VlcF9zY29yZSkKICAKfQpgYGAKCiMgQXNzZXNzaW5nIHBlcmZvcm1hbmNlIGFnYWluc3QgdGhlICJ0cnV0aCIKbm90ZSB0aGF0IHRoaXMgaXMgb25seSBhcyBhY2N1cmF0ZSBhcyB0aGUgYW5ub3RhdGlvbnMgb2YgdGhlIGlucHV0IHNlcXVlbmNlcwoKdGhpcyBmdW5jdGlvbiBjYWxjdWxhdGVzIHRoZSBwcmVjaXNpb24sIHJlY2FsbCwgYW5kIEYxIHNjb3JlIGZvciBlYWNoIHBpcGVsaW5lCmBgYHtyfQphc3Nlc3NfcGVyZm9ybWFuY2UgPC0gZnVuY3Rpb24oc2VxdHlwZSwga2VlcF9zY29yZSkgewogIAogIHRydWVwb3NpdGl2ZSA8LSByZXAoIm5vdCB2aXJhbCIsIGxlbmd0aChzZXF0eXBlKSkKICB0cnVlcG9zaXRpdmVbc2VxdHlwZT09InZpcnVzIl0gPC0gInZpcmFsIgogIAogICNtYWtlIGNvbmZ1c2lvbiBtYXRyaXgKICBjb25mdXNpb25fbWF0cml4IDwtIHJlcCgidHJ1ZSBuZWdhdGl2ZSIsIGxlbmd0aChrZWVwX3Njb3JlKSkKICBjb25mdXNpb25fbWF0cml4W3RydWVwb3NpdGl2ZT09InZpcmFsIiAmIGtlZXBfc2NvcmU8PTFdIDwtICJmYWxzZSBuZWdhdGl2ZSIKICBjb25mdXNpb25fbWF0cml4W3RydWVwb3NpdGl2ZT09InZpcmFsIiAmIGtlZXBfc2NvcmU+PTFdIDwtICJ0cnVlIHBvc2l0aXZlIgogIGNvbmZ1c2lvbl9tYXRyaXhbdHJ1ZXBvc2l0aXZlPT0ibm90IHZpcmFsIiAmIGtlZXBfc2NvcmU+PTFdIDwtICJmYWxzZSBwb3NpdGl2ZSIKICAKICBUUCA8LSB0YWJsZShjb25mdXNpb25fbWF0cml4KVs0XQogIEZQIDwtIHRhYmxlKGNvbmZ1c2lvbl9tYXRyaXgpWzJdCiAgVE4gPC0gdGFibGUoY29uZnVzaW9uX21hdHJpeClbM10KICBGTiA8LSB0YWJsZShjb25mdXNpb25fbWF0cml4KVsxXQogIAogIHByZWNpc2lvbiA8LSBUUC8oVFArRlApCiAgcmVjYWxsIDwtIFRQLyhUUCtGTikKICBGMSA8LSAyKnByZWNpc2lvbipyZWNhbGwvKHByZWNpc2lvbityZWNhbGwpCiAgCiAgTUNDIDwtIChUUCpUTi1GUCpGTikvc3FydChhcy5udW1lcmljKFRQK0ZQKSphcy5udW1lcmljKFRQK0ZOKSphcy5udW1lcmljKFROK0ZQKSphcy5udW1lcmljKFROK0ZOKSkKICAKICBhdWMgPC0gcm91bmQoYXVjKHRydWVwb3NpdGl2ZSwga2VlcF9zY29yZSksNCkKICAKICAjYnkgdHlwZSBtZXRyaWNzCiAgZnVuZ2FsX0ZQIDwtIHRhYmxlKGNvbmZ1c2lvbl9tYXRyaXhbc2VxdHlwZT09ImZ1bmdpIl0pWzJdCiAgcHJvdGlzdF9GUCA8LSB0YWJsZShjb25mdXNpb25fbWF0cml4W3NlcXR5cGU9PSJwcm90aXN0Il0pWzJdCiAgYmFjdGVyaWFsX0ZQIDwtIHRhYmxlKGNvbmZ1c2lvbl9tYXRyaXhbc2VxdHlwZT09ImJhY3RlcmlhIl0pWzJdCiAgdmlyYWxfRk4gPC0gdGFibGUoY29uZnVzaW9uX21hdHJpeFtzZXF0eXBlPT0idmlydXMiXSlbMV0KICAKICBwZXJmb3JtYW5jZSA8LSBjKHByZWNpc2lvbiwgcmVjYWxsLCBGMSwgTUNDLCBhdWMsIGZ1bmdhbF9GUCwgCiAgICAgICAgICAgICAgICAgICBwcm90aXN0X0ZQLCBiYWN0ZXJpYWxfRlAsIHZpcmFsX0ZOKQogIG5hbWVzKHBlcmZvcm1hbmNlKSA8LSBjKCJwcmVjaXNpb24iLCAicmVjYWxsIiwgIkYxIiwgIk1DQyIsICJBVUMiLCAiZnVuZ2FsX0ZQIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAicHJvdGlzdF9GUCIsICJiYWN0ZXJpYWxfRlAiLCAidmlyYWxfRk4iKQogIAogIHJldHVybihwZXJmb3JtYW5jZSkKfQpgYGAKCmNvbWJpbmF0aW9uIG9mIHRvb2xzIGxpc3QKYGBge3J9CmNvbWJvc19saXN0IDwtIGRhdGEuZnJhbWUodG9vbGNvbWJvPXJlcCgwLCA2NCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgdHVuZV9ub3RfdmlyYWw9cmVwKDAsIDY0KSwKICAgICAgICAgICAgICAgICAgICAgICAgICBEVkY9cmVwKDAsIDY0KSwKICAgICAgICAgICAgICAgICAgICAgICAgICB0dW5lX3ZpcmFsPXJlcCgwLCA2NCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgVklCUkFOVD1yZXAoMCwgNjQpLAogICAgICAgICAgICAgICAgICAgICAgICAgIFZTPXJlcCgwLCA2NCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgVlMyPXJlcCgwLCA2NCkpCnAgPC0gMQoKZm9yIChpIGluIGMoMCwxKSl7CiAgZm9yIChqIGluIGMoMCwxKSl7CiAgICBmb3IgKGsgaW4gYygwLDEpKXsKICAgICAgZm9yIChsIGluIGMoMCwxKSl7CiAgICAgICAgZm9yIChtIGluIGMoMCwxKSl7CiAgICAgICAgICBmb3IgKG4gaW4gYygwLDEpKXsKICAgICAgICAgICAgY29tYm9zX2xpc3QkdG9vbGNvbWJvW3BdIDwtIHBhc3RlKGksaixrLGwsbSxuKQogICAgICAgICAgICBjb21ib3NfbGlzdCR0b29sY29tYm8yW3BdIDwtIHBhc3RlKGlmKGkpeyJ0diJ9ZWxzZXsiMCJ9LGlmKGopeyJEVkYifWVsc2V7IjAifSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZihrKXsidG52In1lbHNleyIwIn0saWYobCl7IlZCIn1lbHNleyIwIn0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYobSl7IlZTIn1lbHNleyIwIn0saWYobil7IlZTMiJ9ZWxzZXsiMCJ9KQogICAgICAgICAgICBjb21ib3NfbGlzdCR0dW5lX25vdF92aXJhbFtwXSA8LSBpCiAgICAgICAgICAgIGNvbWJvc19saXN0JERWRltwXSA8LSBqCiAgICAgICAgICAgIGNvbWJvc19saXN0JHR1bmVfdmlyYWxbcF0gPC0gawogICAgICAgICAgICBjb21ib3NfbGlzdCRWSUJSQU5UW3BdIDwtIGwKICAgICAgICAgICAgY29tYm9zX2xpc3QkVlNbcF0gPC0gbQogICAgICAgICAgICBjb21ib3NfbGlzdCRWUzJbcF0gPC0gbgogICAgICAgICAgICBwIDwtIHArMQogICAgICAgICAgfQogICAgICAgIH0KICAgICAgfQogICAgfQogIH0KfQoKY29tYm9zX2xpc3QgPC0gY29tYm9zX2xpc3RbLTEsXQpgYGAKCnRoaXMgZnVuY3Rpb24gYnVpbGRzIGEgbGlzdCBvZiBhbGwgb2YgdGhlIGNvbWJpbmF0aW9ucyB0aGF0IHRoZSB1c2VyIHdhbnRzIHRvIAp0ZXN0LiAKSW4gdGhpcyBjYXNlLCB3ZSdyZSBjb21wYXJpbmcgdGhlIHBlcmZvcm1hbmNlIG9mIGFsbCB1bmlxdWUgY29tYmluYXRpb25zIG9mIHRoZSAKc2l4IHRvb2xzLgpgYGB7cn0KYnVpbGRfc2NvcmVfbGlzdCA8LSBmdW5jdGlvbihpbnB1dF9zZXFzLCBjb21ib3MpIHsKICBvdXRwdXQgPC0gZGF0YS5mcmFtZShwcmVjaXNpb249cmVwKDAsIG5yb3coY29tYm9zKSksCiAgICAgICAgICAgICAgICAgICAgICAgcmVjYWxsPXJlcCgwLCBucm93KGNvbWJvcykpLAogICAgICAgICAgICAgICAgICAgICAgIEYxPXJlcCgwLCBucm93KGNvbWJvcykpLAogICAgICAgICAgICAgICAgICAgICAgIE1DQz1yZXAoMCwgbnJvdyhjb21ib3MpKSwKICAgICAgICAgICAgICAgICAgICAgICBBVUM9cmVwKDAsIG5yb3coY29tYm9zKSksCiAgICAgICAgICAgICAgICAgICAgICAgZnVuZ2FsX0ZQPXJlcCgwLCBucm93KGNvbWJvcykpLAogICAgICAgICAgICAgICAgICAgICAgIHByb3Rpc3RfRlA9cmVwKDAsIG5yb3coY29tYm9zKSksCiAgICAgICAgICAgICAgICAgICAgICAgYmFjdGVyaWFsX0ZQPXJlcCgwLCBucm93KGNvbWJvcykpLAogICAgICAgICAgICAgICAgICAgICAgIHZpcmFsX0ZOPXJlcCgwLCBucm93KGNvbWJvcykpKQogIGZvciAoaSBpbiAxOm5yb3coY29tYm9zKSkgewogICAga2VlcF9zY29yZSA8LSBnZXR0aW5nX3ZpcmFsX3NldF8xKGlucHV0X3NlcXMsIGluY2x1ZGVfdmlicmFudCA9IGNvbWJvcyRWSUJSQU5UW2ldLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyID0gY29tYm9zJFZTW2ldLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyMiA9IGNvbWJvcyRWUzJbaV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV90dW5pbmdfdmlyYWwgPSBjb21ib3MkdHVuZV92aXJhbFtpXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ19ub3RfdmlyYWwgPSBjb21ib3MkdHVuZV9ub3RfdmlyYWxbaV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV9kZWVwdmlyZmluZGVyID0gY29tYm9zJERWRltpXSkKICAKICAgIG91dHB1dFtpLDE6OV0gPC0gYXNzZXNzX3BlcmZvcm1hbmNlKGlucHV0X3NlcXMkc2VxdHlwZSwga2VlcF9zY29yZSkKICAgIAogICAgb3V0cHV0JHRvb2xjb21ib1tpXSA8LSBwYXN0ZShjb21ib3MkdHVuZV92aXJhbFtpXSxjb21ib3MkRFZGW2ldLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb21ib3MkdHVuZV9ub3RfdmlyYWxbaV0sIGNvbWJvcyRWSUJSQU5UW2ldLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb21ib3MkVlNbaV0sIGNvbWJvcyRWUzJbaV0pCiAgfQogIAogIG91dHB1dFtpcy5uYShvdXRwdXQpXSA8LSAwCgogIHJldHVybiAob3V0cHV0KQp9CmBgYAoKIyMgQ2FsY3VsYXRlIHRoZSBwZXJmb3JtYW5jZSBvZiBlYWNoIHBpcGVsaW5lCmBgYHtyfQphY2N1cmFjeV9zY29yZXMgPC0gZGF0YS5mcmFtZSh0ZXN0aW5nX3NldF9pbmRleD1yZXAoMCwgbnJvdyhjb21ib3NfbGlzdCkqMTApLAogICAgICAgICAgICAgICAgICAgICAgcHJlY2lzaW9uPXJlcCgwLCBucm93KGNvbWJvc19saXN0KSoxMCksCiAgICAgICAgICAgICAgICAgICAgICAgcmVjYWxsPXJlcCgwLCBucm93KGNvbWJvc19saXN0KSoxMCksCiAgICAgICAgICAgICAgICAgICAgICAgRjE9cmVwKDAsIG5yb3coY29tYm9zX2xpc3QpKjEwKSwKICAgICAgICAgICAgICAgICAgICAgICBNQ0M9cmVwKDAsIG5yb3coY29tYm9zX2xpc3QpKjEwKSwgCiAgICAgICAgICAgICAgICAgICAgICBBVUM9cmVwKDAsIG5yb3coY29tYm9zX2xpc3QpKjEwKSwKICAgICAgICAgICAgICAgICAgICAgIGZ1bmdhbF9GUD1yZXAoMCwgbnJvdyhjb21ib3NfbGlzdCkqMTApLAogICAgICAgICAgICAgICAgICAgICAgcHJvdGlzdF9GUD1yZXAoMCwgbnJvdyhjb21ib3NfbGlzdCkqMTApLAogICAgICAgICAgICAgICAgICAgICAgYmFjdGVyaWFsX0ZQPXJlcCgwLCBucm93KGNvbWJvc19saXN0KSoxMCksCiAgICAgICAgICAgICAgICAgICAgICB2aXJhbF9GTj1yZXAoMCwgbnJvdyhjb21ib3NfbGlzdCkqMTApKQoKYWNjdXJhY3lfc2NvcmVzIDwtIGNiaW5kKHRlc3Rpbmdfc2V0X2luZGV4PXJlcCgxLCBucm93KGNvbWJvc19saXN0KSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ1aWxkX3Njb3JlX2xpc3QodmlydXNlc1t2aXJ1c2VzJEluZGV4PT0xLF0sIGNvbWJvc19saXN0KSkKZm9yIChpIGluIDI6MTApIHsKICBhY2N1cmFjeV9zY29yZXMgPC0gcmJpbmQoYWNjdXJhY3lfc2NvcmVzLAogICAgICAgICAgICAgICAgICAgICAgICAgICBjYmluZCh0ZXN0aW5nX3NldF9pbmRleD1yZXAoaSwgbnJvdyhjb21ib3NfbGlzdCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBidWlsZF9zY29yZV9saXN0KHZpcnVzZXNbdmlydXNlcyRJbmRleD09aSxdLCBjb21ib3NfbGlzdCkpKQp9CmBgYAoKYGBge3J9CmFjY3VyYWN5X3Njb3JlcyRudW1ydWxlcyA8LSBzdHJfY291bnQoYWNjdXJhY3lfc2NvcmVzJHRvb2xjb21ibywgIjEiKQojYWNjdXJhY3lfc2NvcmVzIDwtIGFjY3VyYWN5X3Njb3Jlc1tvcmRlcihhY2N1cmFjeV9zY29yZXMkbnVtcnVsZXMsIGRlY3JlYXNpbmc9RiksXQphY2N1cmFjeV9zY29yZXMgPC0gYWNjdXJhY3lfc2NvcmVzW29yZGVyKGFjY3VyYWN5X3Njb3JlcyRNQ0MsIGRlY3JlYXNpbmc9RiksXQphY2N1cmFjeV9zY29yZXMkdG9vbGNvbWJvIDwtIGZhY3RvcihhY2N1cmFjeV9zY29yZXMkdG9vbGNvbWJvLCBsZXZlbHMgPSB1bmlxdWUoYWNjdXJhY3lfc2NvcmVzJHRvb2xjb21ibykpCmFjY3VyYWN5X3Njb3JlcyRudW1ydWxlcyA8LSBhcy5mYWN0b3IoYWNjdXJhY3lfc2NvcmVzJG51bXJ1bGVzKQpgYGAKCmBgYHtyfQpwYWwgPC0gZ2d0aGVtZXM6OnRhYmxlYXVfY29sb3JfcGFsKHBhbGV0dGU9IlRhYmxlYXUgMTAiLCB0eXBlPSJyZWd1bGFyIikKYGBgCgoKIyMgVmlzdWFsaXplIGhvdyB0aGUgcHJlY2lzaW9uLCByZWNhbGwsIGFuZCBGMSBzY29yZXMgY2hhbmdlIGFjcm9zcyBwaXBlbGluZXMuCmBgYHtyfQpwMiA8LSBnZ3Bsb3QoYWNjdXJhY3lfc2NvcmVzLCBhZXMoeD10b29sY29tYm8sIHk9RjEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3I9bnVtcnVsZXMsIGZpbGw9bnVtcnVsZXMpKSArCiAgZ2VvbV9wb2ludChhbHBoYT0wLjUpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQsIGFuZ2xlID0gOTApLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgeGxhYigiVG9vbCBDb21iaW5hdGlvbiAodHYsIERWRiwgdG52LCBWQiwgVlMsIFZTMikiKSArCiAgeWxhYigiRjEgU2NvcmUiKQpwMgoKZ2dwbG90KGFjY3VyYWN5X3Njb3JlcywgYWVzKHg9dG9vbGNvbWJvLCB5PXByZWNpc2lvbiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvcj1udW1ydWxlcywgZmlsbD1udW1ydWxlcykpICsKICBnZW9tX3BvaW50KGFscGhhPTAuNSkgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCwgYW5nbGUgPSA5MCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICB4bGFiKCJUb29sIENvbWJpbmF0aW9uICh0diwgRFZGLCB0bnYsIFZCLCBWUywgVlMyKSIpICsKICB5bGFiKCJQcmVjaXNpb24iKQpnZ3Bsb3QoYWNjdXJhY3lfc2NvcmVzLCBhZXMoeD10b29sY29tYm8sIHk9cmVjYWxsLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yPW51bXJ1bGVzLCBmaWxsPW51bXJ1bGVzKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC41KSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0LCBhbmdsZSA9IDkwKSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHhsYWIoIlRvb2wgQ29tYmluYXRpb24gKHR2LCBEVkYsIHRudiwgVkIsIFZTLCBWUzIpIikgKwogIHlsYWIoIlJlY2FsbCIpCgpnZ3Bsb3QoYWNjdXJhY3lfc2NvcmVzLCBhZXMoeD1wcmVjaXNpb24sIHk9cmVjYWxsLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yPW51bXJ1bGVzLCBmaWxsPW51bXJ1bGVzKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC41KSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0LCBhbmdsZSA9IDkwKSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MjApLAogICkgKwogIHhsYWIoIlByZWNpc2lvbiIpICsKICB5bGFiKCJSZWNhbGwiKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg2KSksIDAuNSkpICsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg2KSksIDEpKQoKZ2dwbG90KGFjY3VyYWN5X3Njb3Jlc1thY2N1cmFjeV9zY29yZXMkdGVzdGluZ19zZXRfaW5kZXg9PTEsXSwgYWVzKHg9cHJlY2lzaW9uLCB5PXJlY2FsbCkpICsKICBnZW9tX2xhYmVsKGFscGhhPTAuNywgbGFiZWw9YWNjdXJhY3lfc2NvcmVzJHRvb2xjb21ib1thY2N1cmFjeV9zY29yZXMkdGVzdGluZ19zZXRfaW5kZXg9PTFdKSArCiAgZ2VvbV9wb2ludChhbHBoYT0wLjUsIGFlcyhjb2xvcj1udW1ydWxlcywgZmlsbD1udW1ydWxlcykpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQsIGFuZ2xlID0gOTApLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgeGxhYigiUHJlY2lzaW9uIikgKwogIHlsYWIoIlJlY2FsbCIpCgpnZ3Bsb3QoYWNjdXJhY3lfc2NvcmVzLCBhZXMoeD10b29sY29tYm8sIHk9YWJzKHByZWNpc2lvbi1yZWNhbGwpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yPW51bXJ1bGVzLCBmaWxsPW51bXJ1bGVzKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC41KSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0LCBhbmdsZSA9IDkwKSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHhsYWIoIlRvb2wgQ29tYmluYXRpb24gKHR2LCBEVkYsIHRudiwgVkIsIFZTLCBWUzIpIikgKwogIHlsYWIoIlByZWNpc2lvbi1SZWNhbGwiKQpnZ3Bsb3QoYWNjdXJhY3lfc2NvcmVzLCBhZXMoeD10b29sY29tYm8sIHk9TUNDLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yPW51bXJ1bGVzLCBmaWxsPW51bXJ1bGVzKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC41KSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0LCBhbmdsZSA9IDkwKSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MjApLAogICkgKwogIHhsYWIoIlRvb2wgQ29tYmluYXRpb24gKHR2LCBEVkYsIHRudiwgVkIsIFZTLCBWUzIpIikgKwogIHlsYWIoIk1DQyIpICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSJOdW1iZXIgb2YgUnVsZSBTZXRzIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg2KSksIDAuNSkpICsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZT0iTnVtYmVyIG9mIFJ1bGUgU2V0cyIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNikpLCAxKSkKICAKZ2dwbG90KGFjY3VyYWN5X3Njb3JlcywgYWVzKHg9dG9vbGNvbWJvLCB5PUFVQywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvcj1udW1ydWxlcywgZmlsbD1udW1ydWxlcykpICsKICBnZW9tX3BvaW50KGFscGhhPTAuNSkgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCwgYW5nbGUgPSA5MCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICB4bGFiKCJUb29sIENvbWJpbmF0aW9uICh0diwgRFZGLCB0bnYsIFZCLCBWUywgVlMyKSIpICsKICB5bGFiKCJBVUMiKQpnZ3Bsb3QoYWNjdXJhY3lfc2NvcmVzLCBhZXMoeD10b29sY29tYm8sIHk9ZnVuZ2FsX0ZQLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yPW51bXJ1bGVzLCBmaWxsPW51bXJ1bGVzKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC41KSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0LCBhbmdsZSA9IDkwKSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHhsYWIoIlRvb2wgQ29tYmluYXRpb24gKHR2LCBEVkYsIHRudiwgVkIsIFZTLCBWUzIpIikgKwogIHlsYWIoIkZ1bmdhbCBGYWxzZSBQb3NpdGl2ZXMiKQoKZ2dwbG90KGFjY3VyYWN5X3Njb3JlcywgYWVzKHg9dG9vbGNvbWJvLCB5PXByb3Rpc3RfRlAsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3I9bnVtcnVsZXMsIGZpbGw9bnVtcnVsZXMpKSArCiAgZ2VvbV9wb2ludChhbHBoYT0wLjUpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQsIGFuZ2xlID0gOTApLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgeGxhYigiVG9vbCBDb21iaW5hdGlvbiAodHYsIERWRiwgdG52LCBWQiwgVlMsIFZTMikiKSArCiAgeWxhYigiUHJvdGlzdCBGYWxzZSBQb3NpdGl2ZXMiKQoKZ2dwbG90KGFjY3VyYWN5X3Njb3JlcywgYWVzKHg9dG9vbGNvbWJvLCB5PWJhY3RlcmlhbF9GUCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvcj1udW1ydWxlcywgZmlsbD1udW1ydWxlcykpICsKICBnZW9tX3BvaW50KGFscGhhPTAuNSkgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCwgYW5nbGUgPSA5MCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICB4bGFiKCJUb29sIENvbWJpbmF0aW9uICh0diwgRFZGLCB0bnYsIFZCLCBWUywgVlMyKSIpICsKICB5bGFiKCJCYWN0ZXJpYWwgRmFsc2UgUG9zaXRpdmVzIikKCmdncGxvdChhY2N1cmFjeV9zY29yZXMsIGFlcyh4PXRvb2xjb21ibywgeT12aXJhbF9GTiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvcj1udW1ydWxlcywgZmlsbD1udW1ydWxlcykpICsKICBnZW9tX3BvaW50KGFscGhhPTAuNSkgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCwgYW5nbGUgPSA5MCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICB4bGFiKCJUb29sIENvbWJpbmF0aW9uICh0diwgRFZGLCB0bnYsIFZCLCBWUywgVlMyKSIpICsKICB5bGFiKCJWaXJhbCBGYWxzZSBOZWdhdGl2ZXMiKQoKZ2dwbG90KGFjY3VyYWN5X3Njb3JlcywgYWVzKHg9dG9vbGNvbWJvLCB5PXZpcmFsX0ZOLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yPW51bXJ1bGVzLCBmaWxsPW51bXJ1bGVzKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC41KSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0LCBhbmdsZSA9IDkwKSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHhsYWIoIlRvb2wgQ29tYmluYXRpb24gKHR2LCBEVkYsIHRudiwgVkIsIFZTLCBWUzIpIikgKwogIHlsYWIoIlZpcmFsIEZhbHNlIE5lZ2F0aXZlcyIpCgpnZ3Bsb3QoYWNjdXJhY3lfc2NvcmVzLCBhZXMoeD1wcm90aXN0X0ZQLCB5PWZ1bmdhbF9GUCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvcj1udW1ydWxlcywgZmlsbD1udW1ydWxlcykpICsKICBnZW9tX3BvaW50KGFscGhhPTAuNSkgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCwgYW5nbGUgPSA5MCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICB4bGFiKCJQcm90aXN0IEZQIikgKwogIHlsYWIoIkZ1bmdhbCBGUCIpCgpnZ3Bsb3QoYWNjdXJhY3lfc2NvcmVzLCBhZXMoeD1yZWNhbGwsIHk9ZnVuZ2FsX0ZQLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yPW51bXJ1bGVzLCBmaWxsPW51bXJ1bGVzKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC41KSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0LCBhbmdsZSA9IDkwKSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHhsYWIoIlJlY2FsbCIpICsKICB5bGFiKCJGdW5nYWwgRlAiKQoKZ2dwbG90KGFjY3VyYWN5X3Njb3JlcywgYWVzKHg9cmVjYWxsLCB5PXByb3Rpc3RfRlAsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3I9bnVtcnVsZXMsIGZpbGw9bnVtcnVsZXMpKSArCiAgZ2VvbV9wb2ludChhbHBoYT0wLjUpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQsIGFuZ2xlID0gOTApLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgeGxhYigiUmVjYWxsIikgKwogIHlsYWIoIlByb3Rpc3QgRlAiKQpgYGAKCmBgYHtyfQphY2N1cmFjeV9zY29yZXNfbWVsdCA8LSBhY2N1cmFjeV9zY29yZXMgJT4lIAogIHNlbGVjdCh0ZXN0aW5nX3NldF9pbmRleCwgcHJlY2lzaW9uLCByZWNhbGwsIE1DQywgbnVtcnVsZXMsIHRvb2xjb21ibykgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHM9YyhwcmVjaXNpb24sIHJlY2FsbCwgTUNDKSwgCiAgICAgICAgICAgICAgIG5hbWVzX3RvPSJwZXJmb3JtYW5jZV9tZXRyaWMiLAogICAgICAgICAgICAgICB2YWx1ZXNfdG89InBlcmZvcm1hbmNlX21ldHJpY19zY29yZSIpCmBgYAoKYGBge3J9CmdncGxvdChhY2N1cmFjeV9zY29yZXNfbWVsdFthY2N1cmFjeV9zY29yZXNfbWVsdCRwZXJmb3JtYW5jZV9tZXRyaWM9PSJNQ0MiLF0sIGFlcyh4PW51bXJ1bGVzLCB5PXBlcmZvcm1hbmNlX21ldHJpY19zY29yZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvcj1udW1ydWxlcywgZmlsbD1udW1ydWxlcykpICsKICBnZW9tX2JveHBsb3QoKSArCiAgZ2VvbV9wb2ludChhbHBoYT0wLjUpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQsIGFuZ2xlID0gOTApLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgeWxhYigiTUNDIikgKwogIHhsYWIoIk51bWJlciBvZiBSdWxlIFNldHMiKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg2KSksIDAuNSkpICsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg2KSksIDEpKSAKICAKYGBgCgpgYGB7cn0KZ2dwbG90KGFjY3VyYWN5X3Njb3Jlc19tZWx0W2FjY3VyYWN5X3Njb3Jlc19tZWx0JHBlcmZvcm1hbmNlX21ldHJpYz09InJlY2FsbCIsXSwgYWVzKHg9bnVtcnVsZXMsIHk9cGVyZm9ybWFuY2VfbWV0cmljX3Njb3JlLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yPW51bXJ1bGVzLCBmaWxsPW51bXJ1bGVzKSkgKwogIGdlb21fYm94cGxvdCgpICsKICBnZW9tX3BvaW50KGFscGhhPTAuNSkgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCwgYW5nbGUgPSA5MCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICB5bGFiKCJyZWNhbGwiKSArCiAgeGxhYigiTnVtYmVyIG9mIFJ1bGUgU2V0cyIpICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDYpKSwgMC41KSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDYpKSwgMSkpIAogIApgYGAKCmBgYHtyfQpnZ3Bsb3QoYWNjdXJhY3lfc2NvcmVzX21lbHRbYWNjdXJhY3lfc2NvcmVzX21lbHQkcGVyZm9ybWFuY2VfbWV0cmljIT0iTUNDIixdLCBhZXMoeD1udW1ydWxlcywgeT1wZXJmb3JtYW5jZV9tZXRyaWNfc2NvcmUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3I9bnVtcnVsZXMsIGZpbGw9bnVtcnVsZXMpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC41KSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0LCBhbmdsZSA9IDkwKSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHlsYWIoIlNjb3JlIikgKwogIHhsYWIoIk51bWJlciBvZiBSdWxlIFNldHMiKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg2KSksIDAuNSkpICsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg2KSksIDEpKSArCiAgZmFjZXRfd3JhcCh+cGVyZm9ybWFuY2VfbWV0cmljKQogIApgYGAKY29tcGFyaW5nIG1ldHJpYyB3aXRoIGFuZCB3aXRob3V0IHR1bmluZyBydWxlcwpgYGB7cn0KYWNjdXJhY3lfc2NvcmVzX21lbHQkdHVuaW5nX2luYyA8LSAibm8iCmFjY3VyYWN5X3Njb3Jlc19tZWx0JHR1bmluZ19pbmNbc3Vic3RyaW5nKGFjY3VyYWN5X3Njb3Jlc19tZWx0JHRvb2xjb21ibywgMSwgMSk9PTFdIDwtICJ0diIKYWNjdXJhY3lfc2NvcmVzX21lbHQkdHVuaW5nX2luY1tzdWJzdHJpbmcoYWNjdXJhY3lfc2NvcmVzX21lbHQkdG9vbGNvbWJvLCAzLCAzKT09MV0gPC0gInRudiIKYWNjdXJhY3lfc2NvcmVzX21lbHQkdHVuaW5nX2luY1tzdWJzdHJpbmcoYWNjdXJhY3lfc2NvcmVzX21lbHQkdG9vbGNvbWJvLCAxLCAxKT09MSAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdWJzdHJpbmcoYWNjdXJhY3lfc2NvcmVzX21lbHQkdG9vbGNvbWJvLCAzLCAzKT09MV0gPC0gImJvdGgiCmBgYAoKYGBge3J9CmdncGxvdChhY2N1cmFjeV9zY29yZXNfbWVsdCwgYWVzKHg9dHVuaW5nX2luYywgeT1wZXJmb3JtYW5jZV9tZXRyaWNfc2NvcmUpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yPW51bXJ1bGVzLCBmaWxsPW51bXJ1bGVzKSwgYWxwaGE9MC41KSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0LCBhbmdsZSA9IDkwKSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHlsYWIoIk1DQyIpICsKICB4bGFiKCJOdW1iZXIgb2YgVG9vbHMiKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg2KSksIDAuNSkpICsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg2KSksIDEpKSArCiAgZmFjZXRfd3JhcCh+cGVyZm9ybWFuY2VfbWV0cmljKQoKZ2dwbG90KGFjY3VyYWN5X3Njb3Jlc19tZWx0LCBhZXMoeD10dW5pbmdfaW5jLCB5PXBlcmZvcm1hbmNlX21ldHJpY19zY29yZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3I9bnVtcnVsZXMsIGZpbGw9bnVtcnVsZXMpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC41KSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0LCBhbmdsZSA9IDkwKSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHlsYWIoIk1DQyIpICsKICB4bGFiKCJOdW1iZXIgb2YgVG9vbHMiKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg2KSksIDAuNSkpICsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg2KSksIDEpKSArCiAgZmFjZXRfd3JhcCh+cGVyZm9ybWFuY2VfbWV0cmljKQoKZ2dwbG90KGFjY3VyYWN5X3Njb3Jlc19tZWx0W2FjY3VyYWN5X3Njb3Jlc19tZWx0JHBlcmZvcm1hbmNlX21ldHJpYyE9Ik1DQyIsXSwgYWVzKHg9dHVuaW5nX2luYywgeT1wZXJmb3JtYW5jZV9tZXRyaWNfc2NvcmUpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIGdlb21fYm94cGxvdChhZXMoY29sb3I9bnVtcnVsZXMsIGZpbGw9bnVtcnVsZXMpKSArCiAjIGdlb21fcG9pbnQoYWVzKGNvbG9yPW51bXJ1bGVzLCBmaWxsPW51bXJ1bGVzKSwgYWxwaGE9MC41KSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0LCBhbmdsZSA9IDkwKSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHlsYWIoIlNjb3JlIikgKwogIHhsYWIoIiIpICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDYpKSwgMC4zKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDYpKSwgMC43KSkgKwogIGZhY2V0X3dyYXAofnBlcmZvcm1hbmNlX21ldHJpYykKICAKYGBgCgpgYGB7cn0Kd3JpdGVfdHN2KGFjY3VyYWN5X3Njb3JlcywgIjIwMjIxMDI5X2FjY3VyYWN5X3Njb3Jlcy50c3YiKQpgYGAKCnRvIGRvOiBhZGQgaW4gY2x1c3RlcmluZyBhbmQgb3JkaW5hdGlvbiBsaWtlIGluIHRoZSBkcmlua2luZyB3YXRlciBSIG5vdGVib29rCgojIEV4cGVyaW1lbnRpbmcKCiMjIGhpZ2ggcHJlY2lzaW9uIGV4YW1wbGUKYGBge3J9CnZpcnVzZXMka2VlcF9zY29yZV9oaWdoX3ByZWNpc2lvbiA8LSBnZXR0aW5nX3ZpcmFsX3NldF8xKHZpcnVzZXMsIGluY2x1ZGVfZGVlcHZpcmZpbmRlciA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpYnJhbnQgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIyID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX3ZpcmFsID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX25vdF92aXJhbCA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlciA9IEYpCmBgYAoKCmBgYHtyfQp2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9wcmVjaXNpb24gPC0gInRydWUgbmVnYXRpdmUiCnZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9oaWdoX3ByZWNpc2lvblt2aXJ1c2VzJHNlcXR5cGU9PSJ2aXJ1cyIgJiB2aXJ1c2VzJGtlZXBfc2NvcmVfaGlnaF9wcmVjaXNpb248MV0gPC0gImZhbHNlIG5lZ2F0aXZlIgp2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9wcmVjaXNpb25bdmlydXNlcyRzZXF0eXBlPT0idmlydXMiICYgdmlydXNlcyRrZWVwX3Njb3JlX2hpZ2hfcHJlY2lzaW9uPj0xXSA8LSAidHJ1ZSBwb3NpdGl2ZSIKdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2hpZ2hfcHJlY2lzaW9uW3ZpcnVzZXMkc2VxdHlwZSE9InZpcnVzIiAmIHZpcnVzZXMka2VlcF9zY29yZV9oaWdoX3ByZWNpc2lvbj49MV0gPC0gImZhbHNlIHBvc2l0aXZlIgpgYGAKCnZpc3VhbGl6aW5nIGNvbmZ1c2lvbiBtYXRyaXggYnkgdGF4YQpgYGB7cn0KY29uZnVzaW9uX2J5X3RheGEgPC0gbWVsdCh0YWJsZSh2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9wcmVjaXNpb24sIHZpcnVzZXMkc2VxdHlwZSwgdmlydXNlcyRJbmRleCkpCmNvbG5hbWVzKGNvbmZ1c2lvbl9ieV90YXhhKSA8LSBjKCJjb25mdXNpb25fbWF0cml4IiwgInNlcXR5cGUiLCJJbmRleCIsICJjb3VudCIpCmBgYAoKYGBge3J9Cmxlbmd0aChncmVwKCJ0cnVlIiwgdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2hpZ2hfcHJlY2lzaW9uKSkvbnJvdyh2aXJ1c2VzKQpgYGAKCmBgYHtyfQpwYWwgPC0gZ2d0aGVtZXM6OnRhYmxlYXVfY29sb3JfcGFsKHBhbGV0dGU9IlRhYmxlYXUgMTAiLCB0eXBlPSJyZWd1bGFyIikKYGBgCgpgYGB7cn0KZ2dwbG90KGNvbmZ1c2lvbl9ieV90YXhhLCBhZXMoeT1jb3VudCwgeD1jb25mdXNpb25fbWF0cml4LAogICAgICAgICAgICAgICAgICAgZmlsbD1jb25mdXNpb25fbWF0cml4LAogICAgICAgICAgICAgICAgICAgY29sb3I9Y29uZnVzaW9uX21hdHJpeCkpICsKICBnZW9tX2JveHBsb3QoKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNCkpLCAwLjUpLAogICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJmYWxzZSBuZWdhdGl2ZSIsICJmYWxzZSBwb3NpdGl2ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0cnVlIG5lZ2F0aXZlIiwgInRydWUgcG9zaXRpdmUiKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDQpKSwgMSksCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoImZhbHNlIG5lZ2F0aXZlIiwgImZhbHNlIHBvc2l0aXZlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRydWUgbmVnYXRpdmUiLCAidHJ1ZSBwb3NpdGl2ZSIpKSArCiAgeGxhYigiTnVtYmVyIG9mIFNlcXVlbmNlcyIpICsKICB5bGFiKCIiKSArIAogIGZhY2V0X3dyYXAofnNlcXR5cGUsIHNjYWxlcyA9ICJmcmVlIikKYGBgCnRoaXMgcnVsZSBzZXQgaGFkIHRoZSBoaWdoZXN0IHByZWNpc2lvbiwgYnV0IGFzIHlvdSBjYW4gc2VlLCB0aGlzIGNvbWVzIHdpdGggYSBiaWcgc2FjcmlmaWNlIGluIHJlY2FsbAoKCgojIyBoaWdoIE1DQyBleGFtcGxlCmBgYHtyfQp2aXJ1c2VzJGtlZXBfc2NvcmVfaGlnaF9NQ0MgPC0gZ2V0dGluZ192aXJhbF9zZXRfMSh2aXJ1c2VzLCBpbmNsdWRlX2RlZXB2aXJmaW5kZXIgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aWJyYW50ID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyMiA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ192aXJhbCA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ19ub3RfdmlyYWwgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIgPSBUKQpgYGAKCgpgYGB7cn0KdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2hpZ2hfTUNDIDwtICJ0cnVlIG5lZ2F0aXZlIgp2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9NQ0NbdmlydXNlcyRzZXF0eXBlPT0idmlydXMiICYgdmlydXNlcyRrZWVwX3Njb3JlX2hpZ2hfTUNDPDFdIDwtICJmYWxzZSBuZWdhdGl2ZSIKdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2hpZ2hfTUNDW3ZpcnVzZXMkc2VxdHlwZT09InZpcnVzIiAmIHZpcnVzZXMka2VlcF9zY29yZV9oaWdoX01DQz49MV0gPC0gInRydWUgcG9zaXRpdmUiCnZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9oaWdoX01DQ1t2aXJ1c2VzJHNlcXR5cGUhPSJ2aXJ1cyIgJiB2aXJ1c2VzJGtlZXBfc2NvcmVfaGlnaF9NQ0M+PTFdIDwtICJmYWxzZSBwb3NpdGl2ZSIKYGBgCgphY2N1cmFjeToKYGBge3J9Cmxlbmd0aChncmVwKCJ0cnVlIiwgdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2hpZ2hfTUNDKSkvbnJvdyh2aXJ1c2VzKQpgYGAKCnJlY2FsbApgYGB7cn0KbGVuZ3RoKGdyZXAoInRydWUgcG9zaXRpdmUiLCB2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9NQ0MpKS9sZW5ndGgoZ3JlcCgidmlydXMiLCB2aXJ1c2VzJHNlcXR5cGUpKQpgYGAKCmBgYHtyfQpUUCA8LSB0YWJsZSh2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9NQ0MpWzRdCkZQIDwtIHRhYmxlKHZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9oaWdoX01DQylbMl0KVE4gPC0gdGFibGUodmlydXNlcyRjb25mdXNpb25fbWF0cml4X2hpZ2hfTUNDKVszXQpGTiA8LSB0YWJsZSh2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9NQ0MpWzFdCgpwcmVjaXNpb24gPC0gYXMubnVtZXJpYyhUUC8oVFArRlApKQpwcmVjaXNpb24KcmVjYWxsIDwtIGFzLm51bWVyaWMoVFAvKFRQK0ZOKSkKcmVjYWxsCkYxIDwtIGFzLm51bWVyaWMoMipwcmVjaXNpb24qcmVjYWxsLyhwcmVjaXNpb24rcmVjYWxsKSkKRjEKCk1DQyA8LSBhcy5udW1lcmljKChUUCpUTi1GUCpGTikvc3FydChhcy5udW1lcmljKFRQK0ZQKSphcy5udW1lcmljKFRQK0ZOKSphcy5udW1lcmljKFROK0ZQKSphcy5udW1lcmljKFROK0ZOKSkpCk1DQwpgYGAKCnByZWNpc2lvbj02OSUsIHJlY2FsbD04NyUsIE1DQz03NyUKCnByZWNpc2lvbiBhZGp1c3Rpbmcgc2l6ZSB0byBiZSBlcXVhbCB2aXJhbC9ub3QgdmlyYWwKYGBge3J9ClRQIDwtIHRhYmxlKHZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9oaWdoX01DQylbNF0KRlAgPC0gdGFibGUodmlydXNlcyRjb25mdXNpb25fbWF0cml4X2hpZ2hfTUNDKVsyXSouMTEKVE4gPC0gdGFibGUodmlydXNlcyRjb25mdXNpb25fbWF0cml4X2hpZ2hfTUNDKVszXSouMTEKRk4gPC0gdGFibGUodmlydXNlcyRjb25mdXNpb25fbWF0cml4X2hpZ2hfTUNDKVsxXQoKcHJlY2lzaW9uIDwtIGFzLm51bWVyaWMoVFAvKFRQK0ZQKSkKcHJlY2lzaW9uCnJlY2FsbCA8LSBhcy5udW1lcmljKFRQLyhUUCtGTikpCnJlY2FsbApGMSA8LSBhcy5udW1lcmljKDIqcHJlY2lzaW9uKnJlY2FsbC8ocHJlY2lzaW9uK3JlY2FsbCkpCkYxCgpNQ0MgPC0gYXMubnVtZXJpYygoVFAqVE4tRlAqRk4pL3NxcnQoYXMubnVtZXJpYyhUUCtGUCkqYXMubnVtZXJpYyhUUCtGTikqYXMubnVtZXJpYyhUTitGUCkqYXMubnVtZXJpYyhUTitGTikpKQpNQ0MKYGBgCgpwcmVjaXNpb249MC45NSwgcmVjYWxsPTAuODcsIEYxPTAuOTEsIE1DQz0wLjgyCgoKCgoKdmlzdWFsaXppbmcgY29uZnVzaW9uIG1hdHJpeCBieSB0YXhhCmBgYHtyfQpjb25mdXNpb25fYnlfdGF4YSA8LSB2aXJ1c2VzICU+JSBjb3VudChjb25mdXNpb25fbWF0cml4X2hpZ2hfTUNDLCBzZXF0eXBlLCBJbmRleCkKCmNvbG5hbWVzKGNvbmZ1c2lvbl9ieV90YXhhKSA8LSBjKCJjb25mdXNpb25fbWF0cml4IiwgInNlcXR5cGUiLCJpbmRleCIsICJjb3VudCIpCmBgYAoKCmBgYHtyfQpwYWwgPC0gZ2d0aGVtZXM6OnRhYmxlYXVfY29sb3JfcGFsKHBhbGV0dGU9IlRhYmxlYXUgMTAiLCB0eXBlPSJyZWd1bGFyIikKYGBgCgpgYGB7cn0KZ2dwbG90KGNvbmZ1c2lvbl9ieV90YXhhLCBhZXMoeD1jb3VudCwgeT1hcy5mYWN0b3IoaW5kZXgpLAogICAgICAgICAgICAgICAgICAgZmlsbD1jb25mdXNpb25fbWF0cml4LAogICAgICAgICAgICAgICAgICAgY29sb3I9Y29uZnVzaW9uX21hdHJpeCkpICsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg0KSksIDAuNSksCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoImZhbHNlIG5lZ2F0aXZlIiwgImZhbHNlIHBvc2l0aXZlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRydWUgbmVnYXRpdmUiLCAidHJ1ZSBwb3NpdGl2ZSIpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNCkpLCAxKSwKICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiZmFsc2UgbmVnYXRpdmUiLCAiZmFsc2UgcG9zaXRpdmUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidHJ1ZSBuZWdhdGl2ZSIsICJ0cnVlIHBvc2l0aXZlIikpICsKICB4bGFiKCJOdW1iZXIgb2YgU2VxdWVuY2VzIikgKwogIHlsYWIoIiIpICsgCiAgZmFjZXRfd3JhcCh+c2VxdHlwZSwgc2NhbGVzID0gImZyZWUiKSArCiAgY29vcmRfZmxpcCgpCmBgYAoKCmRpZmZlcmVuY2VzIGJhc2VkIG9uIGdlbm9tZSBzaXplCmBgYHtyfQp2aXJ1c2VzJHNpemVfY2xhc3MgPC0gIjMtNWtiIgp2aXJ1c2VzJHNpemVfY2xhc3NbdmlydXNlcyRjaGVja3ZfbGVuZ3RoPjUwMDBdIDwtICI1LTEwa2IiCnZpcnVzZXMkc2l6ZV9jbGFzc1t2aXJ1c2VzJGNoZWNrdl9sZW5ndGg+MTAwMDBdIDwtICI+MTBrYiIKYGBgCgpgYGB7cn0KY29uZnVzaW9uX2J5X3RheGEgPC0gdmlydXNlcyAlPiUgY291bnQoY29uZnVzaW9uX21hdHJpeF9oaWdoX01DQywgc2VxdHlwZSwgc2l6ZV9jbGFzcywgSW5kZXgpCgpjb2xuYW1lcyhjb25mdXNpb25fYnlfdGF4YSkgPC0gYygiY29uZnVzaW9uX21hdHJpeCIsICJzZXF0eXBlIiwic2l6ZSIsICJpbmRleCIsICJjb3VudCIpCmBgYAoKYGBge3J9CmNvbmZ1c2lvbl92aXJfY2FsbGVkIDwtIGNvbmZ1c2lvbl9ieV90YXhhICU+JSBmaWx0ZXIoY29uZnVzaW9uX21hdHJpeD09InRydWUgcG9zaXRpdmUiIHwgY29uZnVzaW9uX21hdHJpeD09ImZhbHNlIHBvc2l0aXZlIikgCgp0eXBlX2NvdW50IDwtIHZpcnVzZXMgJT4lIGNvdW50KHNlcXR5cGUsIHNpemVfY2xhc3MsIEluZGV4KQoKY29uZnVzaW9uX3Zpcl9jYWxsZWQkcGVyX3ZpcmFsIDwtIDAKCmZvciAoaSBpbiBjKDE6bnJvdyhjb25mdXNpb25fdmlyX2NhbGxlZCkpKSB7CiAgY29uZnVzaW9uX3Zpcl9jYWxsZWQkcGVyX3ZpcmFsW2ldIDwtIGNvbmZ1c2lvbl92aXJfY2FsbGVkJGNvdW50W2ldL3R5cGVfY291bnQkblt0eXBlX2NvdW50JHNlcXR5cGU9PWNvbmZ1c2lvbl92aXJfY2FsbGVkJHNlcXR5cGVbaV0gJiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHlwZV9jb3VudCRJbmRleD09Y29uZnVzaW9uX3Zpcl9jYWxsZWQkaW5kZXhbaV0gJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlX2NvdW50JHNpemVfY2xhc3M9PWNvbmZ1c2lvbl92aXJfY2FsbGVkJHNpemVbaV1dKjEwMAp9Cgpjb25mdXNpb25fdmlyX2NhbGxlZCA8LSBjb25mdXNpb25fdmlyX2NhbGxlZCAlPiUgZ3JvdXBfYnkoc2VxdHlwZSwgc2l6ZSkgJT4lCiAgc3VtbWFyaXNlKG1lYW49bWVhbihwZXJfdmlyYWwpLCAKICAgICAgICAgICAgc2Q9c2QocGVyX3ZpcmFsKSkKCmNvbmZ1c2lvbl92aXJfY2FsbGVkJHNpemUgPC0gZmFjdG9yKGNvbmZ1c2lvbl92aXJfY2FsbGVkJHNpemUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoIjMtNWtiIiwgIjUtMTBrYiIsICI+MTBrYiIpKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoY29uZnVzaW9uX3Zpcl9jYWxsZWQsIGFlcyh5PW1lYW4sIHg9c2l6ZSwKICAgICAgICAgICAgICAgICAgIGZpbGw9c2VxdHlwZSwKICAgICAgICAgICAgICAgICAgIGNvbG9yPXNlcXR5cGUpKSArCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiLCBwb3NpdGlvbj1wb3NpdGlvbl9kb2RnZSgpKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIGdlb21fZXJyb3JiYXIoYWVzKHltaW49bWVhbi1zZCwgeW1heD1tZWFuK3NkKSwgd2lkdGg9LjIsCiAgICAgICAgICAgICAgICAgcG9zaXRpb249cG9zaXRpb25fZG9kZ2UoLjkpKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg2KSksIDAuNSkpICsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg2KSksIDEpKSArCiAgeGxhYigiTGVuZ3RoIikgKwogIHlsYWIoIlNlcXVlbmNlcyBDYWxsZWQgVmlyYWwgKCUpIikgCmBgYAoKCgpgYGB7cn0KdmlydXNlcyRrZWVwX3Njb3JlX3ZiIDwtIGdldHRpbmdfdmlyYWxfc2V0XzEodmlydXNlcywgaW5jbHVkZV9kZWVwdmlyZmluZGVyID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlicmFudCA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlcjIgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV90dW5pbmdfdmlyYWwgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV90dW5pbmdfbm90X3ZpcmFsID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyID0gRikKCnZpcnVzZXMka2VlcF9zY29yZV92Yl9kdmYgPC0gZ2V0dGluZ192aXJhbF9zZXRfMSh2aXJ1c2VzLCBpbmNsdWRlX2RlZXB2aXJmaW5kZXIgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aWJyYW50ID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyMiA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ192aXJhbCA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ19ub3RfdmlyYWwgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIgPSBGKQoKdmlydXNlcyRrZWVwX3Njb3JlX3ZiX2R2Zl92czIgPC0gZ2V0dGluZ192aXJhbF9zZXRfMSh2aXJ1c2VzLCBpbmNsdWRlX2RlZXB2aXJmaW5kZXIgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aWJyYW50ID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyMiA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ192aXJhbCA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ19ub3RfdmlyYWwgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIgPSBGKQoKdmlydXNlcyRrZWVwX3Njb3JlX3ZiX2R2Zl92czJfdnMgPC0gZ2V0dGluZ192aXJhbF9zZXRfMSh2aXJ1c2VzLCBpbmNsdWRlX2RlZXB2aXJmaW5kZXIgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aWJyYW50ID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyMiA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ192aXJhbCA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ19ub3RfdmlyYWwgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIgPSBUKQoKdmlydXNlcyRrZWVwX3Njb3JlX3ZiX2R2Zl92czJfdnNfdHYgPC0gZ2V0dGluZ192aXJhbF9zZXRfMSh2aXJ1c2VzLCBpbmNsdWRlX2RlZXB2aXJmaW5kZXIgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aWJyYW50ID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyMiA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ192aXJhbCA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ19ub3RfdmlyYWwgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIgPSBUKQoKdmlydXNlcyRrZWVwX3Njb3JlX3ZiX2R2Zl92czJfdnNfdHZfdG52IDwtIGdldHRpbmdfdmlyYWxfc2V0XzEodmlydXNlcywgaW5jbHVkZV9kZWVwdmlyZmluZGVyID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlicmFudCA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlcjIgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV90dW5pbmdfdmlyYWwgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV90dW5pbmdfbm90X3ZpcmFsID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyID0gVCkKYGBgCgpgYGB7cn0KdmlydXNlcyR0cnVlX3ZpcnVzIDwtICJub3QiCnZpcnVzZXMkdHJ1ZV92aXJ1c1t2aXJ1c2VzJHNlcXR5cGU9PSJ2aXJ1cyJdIDwtICJ2aXJ1cyIKCnZpcnVzZXNfbG9uZ19zY29yZXMgPC0gdmlydXNlcyAlPiUgCiAgc2VsZWN0KGNvbnRhaW5zKCJrZWVwX3Njb3JlX3ZiIiksIHNpemVfY2xhc3MsIHRydWVfdmlydXMpICU+JQogIHBpdm90X2xvbmdlcihjb2xzPWNvbnRhaW5zKCJrZWVwX3Njb3JlXyIpLCAKICAgICAgICAgICAgICAgbmFtZXNfdG89InJ1bGVfY29tYmluYXRpb24iLAogICAgICAgICAgICAgICB2YWx1ZXNfdG89InZpcmFsX3Njb3JlIikgJT4lIAogIG11dGF0ZSh2aXJhbF9zY29yZT1hcy5mYWN0b3Iocm91bmQodmlyYWxfc2NvcmUpKSkgJT4lCiAgZ3JvdXBfYnkocnVsZV9jb21iaW5hdGlvbiwgdmlyYWxfc2NvcmUsIHNpemVfY2xhc3MsIHRydWVfdmlydXMpICU+JQogIHN1bW1hcmlzZShuID0gbigpKQoKdmlydXNlc19sb25nX3Njb3JlcyRzaXplX2NsYXNzIDwtIGZhY3Rvcih2aXJ1c2VzX2xvbmdfc2NvcmVzJHNpemVfY2xhc3MsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoIjMtNWtiIiwgIjUtMTBrYiIsICI+MTBrYiIpKQpgYGAKCmBgYHtyfQp2aXJ1c2VzX2xvbmdfc2NvcmVzX2FkZGl0aW9uIDwtIHZpcnVzZXNfbG9uZ19zY29yZXNbKHZpcnVzZXNfbG9uZ19zY29yZXMkdHJ1ZV92aXJ1cz09InZpcnVzIiAmICAodmlydXNlc19sb25nX3Njb3JlcyRydWxlX2NvbWJpbmF0aW9uIT0ia2VlcF9zY29yZV92Yl9kdmZfdnMyX3ZzX3R2X3RudiIpICYgdmlydXNlc19sb25nX3Njb3JlcyR2aXJhbF9zY29yZSE9IjAiKSxdCmdncGxvdCh2aXJ1c2VzX2xvbmdfc2NvcmVzX2FkZGl0aW9uLCBhZXMoeT1uLCB4PXJ1bGVfY29tYmluYXRpb24sCiAgICAgICAgICAgICAgICAgICBmaWxsPXZpcmFsX3Njb3JlKSkgKwogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikgKwogIHRoZW1lX2xpZ2h0KCkgKwogIGNvb3JkX2ZsaXAoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIgogICkgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiUHVycGxlcyIpICsKICB4bGFiKCIiKSArCiAgeWxhYigiTnVtYmVyIG9mIFNlcXVlbmNlcyIpICsgCiAgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHM9YygiVkIiLCAiVkIrRFZGIiwgIlZCK0RWRitWUzIiLCAiVkIrRFZGK1ZTMitWUyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiVkIrRFZGK1ZTMitWUythZGRpdGlvbiIpKSArCiAgZmFjZXRfZ3JpZCh+dHJ1ZV92aXJ1cywgc2NhbGVzID0gImZyZWUiKQpgYGAKCmBgYHtyfQpnZ3Bsb3QodmlydXNlc19sb25nX3Njb3JlcywgYWVzKHk9biwgeD1ydWxlX2NvbWJpbmF0aW9uLAogICAgICAgICAgICAgICAgICAgZmlsbD12aXJhbF9zY29yZSkpICsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsKICB0aGVtZV9saWdodCgpICsKICBjb29yZF9mbGlwKCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIKICApICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlB1T3IiLCApICsKICB4bGFiKCIiKSArCiAgeWxhYigiTnVtYmVyIG9mIFNlcXVlbmNlcyIpICsgCiAgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHM9YygiVkIiLCAiVkIrRFZGIiwgIlZCK0RWRitWUzIiLCAiVkIrRFZGK1ZTMitWUyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiVkIrRFZGK1ZTMitWUythZGRpdGlvbiIsICJWQitEVkYrVlMyK1ZTK2FkZGl0aW9uLXJlbW92YWwiKSkgKwogIGZhY2V0X2dyaWQofnRydWVfdmlydXMsIHNjYWxlcyA9ICJmcmVlIikKYGBgCgpgYGB7cn0KZ2dwbG90KHZpcnVzZXNfbG9uZ19zY29yZXMsIGFlcyh5PW4sIHg9cnVsZV9jb21iaW5hdGlvbiwKICAgICAgICAgICAgICAgICAgIGZpbGw9dmlyYWxfc2NvcmUpKSArCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgY29vcmRfZmxpcCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iCiAgKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJQdU9yIiwgKSArCiAgeGxhYigiIikgKwogIHlsYWIoIk51bWJlciBvZiBTZXF1ZW5jZXMiKSArIAogIGZhY2V0X2dyaWQoc2l6ZV9jbGFzc350cnVlX3ZpcnVzLCBzY2FsZXMgPSAiZnJlZSIpCmBgYAoKCiMgQ29uc2lkZXJpbmcgaG93IGVhY2ggbWV0aG9kIGNvbnRyaWJ1dGVzIHRvIHRoZSBmaW5hbCBwcmVkaWN0aW9uCmBgYHtyfQp2aXJ1c2VzX2hpZ2ggPC0gdmlydXNlc1t2aXJ1c2VzJGtlZXBfc2NvcmVfdmJfZHZmX3ZzMl92c190dj49MSxdIAp2aXJ1c2VzX2hpZ2hfbW9kIDwtIHZpcnVzZXNfaGlnaCAlPiUgc2VsZWN0KGtlZXBfc2NvcmVfdmIsa2VlcF9zY29yZV92Yl9kdmYsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtlZXBfc2NvcmVfdmJfZHZmX3ZzMiwga2VlcF9zY29yZV92Yl9kdmZfdnMyX3ZzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrZWVwX3Njb3JlX3ZiX2R2Zl92czJfdnNfdHYsIGtlZXBfc2NvcmVfdmJfZHZmX3ZzMl92c190dl90bnYpCiN2aXJ1c2VzX2hpZ2hfbW9kIDwtIGFwcGx5KHZpcnVzZXNfaGlnaF9tb2QsIGMoMSwyKSwgZnVuY3Rpb24oeCkge2lmICh4ID49IDEpIHt4IDwtIDF9IGVsc2Uge3ggPC0gMH19KQp2aXJ1c2VzX2hpZ2hfbW9kIDwtIGFzX3RpYmJsZSh2aXJ1c2VzX2hpZ2hfbW9kKQoKCmBgYAoKYGBge3J9CnNtX20gPC0gcmVzaGFwZTI6Om1lbHQodmlydXNlc19oaWdoX21vZCkKY29sbmFtZXMoc21fbSkgPC0gYygibWV0aG9kIiwgInZpcmFsX3Njb3JlIikKYGBgCgpgYGB7cn0Kc21fbSA8LSBzbV9tW3NtX20kdmlyYWxfc2NvcmU+MCxdCgpzbV9tJHNjb3JlIDwtIHNtX20kdmlyYWxfc2NvcmUKCnNtX20kc2NvcmVbc21fbSR2aXJhbF9zY29yZT09MC41XSA8LSAiMC41IgpzbV9tJHNjb3JlW3NtX20kdmlyYWxfc2NvcmU+PTFdIDwtICIxIgpzbV9tJHNjb3JlW3NtX20kdmlyYWxfc2NvcmU+PTJdIDwtICIyIgpzbV9tJHNjb3JlW3NtX20kdmlyYWxfc2NvcmU+PTNdIDwtICIzIgpzbV9tJHNjb3JlW3NtX20kdmlyYWxfc2NvcmU+PTRdIDwtICI0IgpzbV9tJHNjb3JlW3NtX20kdmlyYWxfc2NvcmU+PTVdIDwtICI1IgoKc21fbSRzY29yZSA8LSBmYWN0b3Ioc21fbSRzY29yZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscz1jKCIwLjUiLCAiMSIsICIyIiwiMyIsIjQiLCI1IikpCmBgYAoKCmBgYHtyfQpnZ3Bsb3Qoc21fbSwgYWVzKHg9bWV0aG9kLCB5PXNjb3JlLAogICAgICAgICAgICAgICAgICAgZmlsbD1zY29yZSkpICsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsKICB0aGVtZV9saWdodCgpICsKICBjb29yZF9mbGlwKCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIKICApICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lID0gJ1ZpcmFsIFNjb3JlJywKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEoYyh2aXJpZGlzKDYpKSwgMSkpICsKICB4bGFiKCIiKSArCiAgeWxhYigiTnVtYmVyIG9mIFNlcXVlbmNlcyIpICsKICBjb29yZF9mbGlwKCkKYGBgCiMjIEFub3RoZXIgd2F5IG9mIHZpc3VhbGl6aW5nIGJldHdlZW4gcnVsZSBzZXRzCgpgYGB7cn0KdmlydXNlc19tY2NfYWxsdXZpYWwgPC0gZGF0YS5mcmFtZShzZXF0eXBlPXZpcnVzZXMkc2VxdHlwZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrZWVwX3Njb3JlX2hpZ2hfTUNDPXZpcnVzZXMka2VlcF9zY29yZV9oaWdoX01DQywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb25mdXNpb25fbWF0cml4X2hpZ2hfTUNDPXZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9oaWdoX01DQykKCgp2aXJ1c2VzX21jY19hbGx1dmlhbCRrZWVwX3Njb3JlX3ZiIDwtIGdldHRpbmdfdmlyYWxfc2V0XzEodmlydXNlcywgaW5jbHVkZV9kZWVwdmlyZmluZGVyID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlicmFudCA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlcjIgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV90dW5pbmdfdmlyYWwgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV90dW5pbmdfbm90X3ZpcmFsID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyID0gRikKCnZpcnVzZXNfbWNjX2FsbHV2aWFsJGtlZXBfc2NvcmVfZHZmIDwtIGdldHRpbmdfdmlyYWxfc2V0XzEodmlydXNlcywgaW5jbHVkZV9kZWVwdmlyZmluZGVyID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlicmFudCA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlcjIgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV90dW5pbmdfdmlyYWwgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV90dW5pbmdfbm90X3ZpcmFsID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyID0gRikKCnZpcnVzZXNfbWNjX2FsbHV2aWFsJGtlZXBfc2NvcmVfdnMgPC0gZ2V0dGluZ192aXJhbF9zZXRfMSh2aXJ1c2VzLCBpbmNsdWRlX2RlZXB2aXJmaW5kZXIgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aWJyYW50ID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyMiA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ192aXJhbCA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ19ub3RfdmlyYWwgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIgPSBUKQoKdmlydXNlc19tY2NfYWxsdXZpYWwka2VlcF9zY29yZV92czIgPC0gZ2V0dGluZ192aXJhbF9zZXRfMSh2aXJ1c2VzLCBpbmNsdWRlX2RlZXB2aXJmaW5kZXIgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aWJyYW50ID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyMiA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ192aXJhbCA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ19ub3RfdmlyYWwgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIgPSBGKQoKdmlydXNlc19tY2NfYWxsdXZpYWwka2VlcF9zY29yZV90diA8LSBnZXR0aW5nX3ZpcmFsX3NldF8xKHZpcnVzZXMsIGluY2x1ZGVfZGVlcHZpcmZpbmRlciA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpYnJhbnQgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIyID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX3ZpcmFsID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX25vdF92aXJhbCA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlciA9IEYpCgp2aXJ1c2VzX21jY19hbGx1dmlhbCRrZWVwX3Njb3JlX3RudiA8LSBnZXR0aW5nX3ZpcmFsX3NldF8xKHZpcnVzZXMsIGluY2x1ZGVfZGVlcHZpcmZpbmRlciA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpYnJhbnQgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIyID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX3ZpcmFsID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX25vdF92aXJhbCA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlciA9IEYpCmBgYAoKYGBge3J9CnZpcnVzZXNfbWNjX2FsbHV2aWFsICU+JQogIGNvdW50KHNlcXR5cGUsIGtlZXBfc2NvcmVfaGlnaF9NQ0MpICU+JSAKICBzcHJlYWQoa2V5ID0ga2VlcF9zY29yZV9oaWdoX01DQywgdmFsdWU9bikKYGBgCgoKYGBge3J9CnZpcnVzZXNfbWNjX2FsbHV2aWFsIDwtIHZpcnVzZXNfbWNjX2FsbHV2aWFsICU+JQogIGNvdW50KHNlcXR5cGUsIGtlZXBfc2NvcmVfZHZmLCBrZWVwX3Njb3JlX3ZiLCBrZWVwX3Njb3JlX3ZzLAogICAgICAgIGtlZXBfc2NvcmVfdnMyLCBrZWVwX3Njb3JlX3R2LCBrZWVwX3Njb3JlX3Rudiwga2VlcF9zY29yZV9oaWdoX01DQykgJT4lCiAgbXV0YXRlKGhpZ2hfbWNjX3ZpcmFsX3Njb3JlPWZhY3Rvcihyb3VuZChrZWVwX3Njb3JlX2hpZ2hfTUNDKSkpCmBgYAoKYGBge3J9CmdncGxvdCh2aXJ1c2VzX21jY19hbGx1dmlhbCwKICAgICAgIGFlcyhheGlzMSA9IGtlZXBfc2NvcmVfZHZmLCBheGlzMiA9IGtlZXBfc2NvcmVfdmIsIAogICAgICAgICAgIGF4aXMzID0ga2VlcF9zY29yZV92cywgYXhpczQgPSBrZWVwX3Njb3JlX3ZzMiwgCiAgICAgICAgICAgYXhpczUgPSBrZWVwX3Njb3JlX3R2LCBheGlzNiA9IGtlZXBfc2NvcmVfdG52LCAKICAgICAgICAgICB5PW4pKSArCiAgZ2VvbV9hbGx1dml1bShhZXMoZmlsbD1oaWdoX21jY192aXJhbF9zY29yZSksCiAgICAgICAgICAgICAgICB3aWR0aCA9IDAsIGtub3QucG9zID0gMCwgcmV2ZXJzZSA9IEZBTFNFKSArCiAgZ2VvbV9zdHJhdHVtKHdpZHRoID0gMS81KSArCiAgdGhlbWVfYncoKSArCiAgZ2VvbV90ZXh0KHN0YXQgPSAic3RyYXR1bSIsIGFlcyhsYWJlbCA9IGFmdGVyX3N0YXQoc3RyYXR1bSkpLAogICAgICAgICAgICByZXZlcnNlID0gRkFMU0UpICsKICB0aGVtZSgKICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCwgYW5nbGUgPSA5MCkKICAgICAgICApICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPWMoMSwyLDMsNCw1LDYpLAogICAgbGFiZWxzPWMoImR2ZiIsICJraiIsICJ2cyIsICJ2czIiLAogICAgICAgICAgICAgInR2IiwgInRudiIpKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJQdU9yIiwgKSArCiAgZmFjZXRfd3JhcCh+c2VxdHlwZSwgc2NhbGVzPSJmcmVlX3kiKSAKYGBgIAoKCgoKCgojIyBWaXN1YWxpemluZyBjb25mdXNpb24gbWF0cml4IGJ5IG51bWJlciBvZiB0b29scwoKCmBgYHtyfQp2aXJ1c2VzJGtlZXBfc2NvcmVfdmlzdWFsaXplIDwtIHZpcnVzZXMka2VlcF9zY29yZV9oaWdoX01DQwp2aXJ1c2VzJGtlZXBfc2NvcmVfdmlzdWFsaXplW3ZpcnVzZXMka2VlcF9zY29yZV9oaWdoX01DQz4xXSA8LSAiPiAxIgp2aXJ1c2VzJGtlZXBfc2NvcmVfdmlzdWFsaXplW3ZpcnVzZXMka2VlcF9zY29yZV9oaWdoX01DQz09MV0gPC0gIjEiCnZpcnVzZXMka2VlcF9zY29yZV92aXN1YWxpemVbdmlydXNlcyRrZWVwX3Njb3JlX2hpZ2hfTUNDPT0wLjVdIDwtICIwLjUiCnZpcnVzZXMka2VlcF9zY29yZV92aXN1YWxpemVbdmlydXNlcyRrZWVwX3Njb3JlX2hpZ2hfTUNDPT0wXSA8LSAiMCIKdmlydXNlcyRrZWVwX3Njb3JlX3Zpc3VhbGl6ZVt2aXJ1c2VzJGtlZXBfc2NvcmVfaGlnaF9NQ0M9PS0wLjVdIDwtICItMC41Igp2aXJ1c2VzJGtlZXBfc2NvcmVfdmlzdWFsaXplW3ZpcnVzZXMka2VlcF9zY29yZV9oaWdoX01DQz09LTFdIDwtICItMSIKdmlydXNlcyRrZWVwX3Njb3JlX3Zpc3VhbGl6ZVt2aXJ1c2VzJGtlZXBfc2NvcmVfaGlnaF9NQ0M8PS0xXSA8LSAiPCAtMSIKCnZpcnVzZXMka2VlcF9zY29yZV92aXN1YWxpemUgPC0gZmFjdG9yKHZpcnVzZXMka2VlcF9zY29yZV92aXN1YWxpemUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHM9YygiPCAtMSIsICItMSIsICItMC41IiwgIjAiLCAiMC41IiwiMSIsICI+IDEiKSkKI3ZpcnVzZXMka2VlcF9zY29yZV92aXN1YWxpemUgPC0gZmFjdG9yKHZpcnVzZXMka2VlcF9zY29yZV92aXN1YWxpemUsIAojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoIuKJpCAwIiwgIuKJpCAwIiwgIuKJpCAwIiwgIjAuNSIsIjEiLCAiPiAxIikpCmBgYAoKYGBge3J9CmxldmVscyhmYWN0b3IodmlydXNlcyRrZWVwX3Njb3JlX3Zpc3VhbGl6ZSkpCmBgYAoKYGBge3J9CmdncGxvdCh2aXJ1c2VzLCBhZXMoeD1hcy5mYWN0b3IoSW5kZXgpLAogICAgICAgICAgICAgICAgICAgZmlsbD1rZWVwX3Njb3JlX3Zpc3VhbGl6ZSwgY29sb3I9a2VlcF9zY29yZV92aXN1YWxpemUpKSArCiAgZ2VvbV9iYXIoc3RhdD0iY291bnQiLCBwb3NpdGlvbj0ic3RhY2siKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgY29vcmRfZmxpcCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNikKICApICsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZSA9ICdWaXJhbCBTY29yZScsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKGModmlyaWRpcyg2KSksIDEpKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZSA9ICdWaXJhbCBTY29yZScsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKGModmlyaWRpcyg2KSksIDAuNSkpICsKICB4bGFiKCJJbmRleCIpICsKICB5bGFiKCJTZXF1ZW5jZSBDb3VudCIpICsKICBmYWNldF93cmFwKH5jb25mdXNpb25fbWF0cml4X2hpZ2hfTUNDLCBzY2FsZXMgPSAiZnJlZSIpCgpgYGAKCiMjIGNsdXN0ZXJpbmcKYGBge3J9CnZpcmFsX3Njb3JlcyA8LSBtYXRyaXgoZGF0YT0wLCBucm93PW5yb3codmlydXNlcyksIG5jb2w9bnJvdyhjb21ib3NfbGlzdCkpCm51bV92aXJ1c2VzIDwtIGRhdGEuZnJhbWUodG9vbGNvbWJvPXJlcCgwLCBucm93KGNvbWJvc19saXN0KSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgbnVtX3ZpcnVzZXM9cmVwKDAsIG5yb3coY29tYm9zX2xpc3QpKSkKCmZvciAoaSBpbiAxOm5yb3coY29tYm9zX2xpc3QpKSB7CiAgdmlyYWxfc2NvcmVzWyxpXSA8LSBnZXR0aW5nX3ZpcmFsX3NldF8xKHZpcnVzZXMsIGluY2x1ZGVfdmlicmFudCA9IGNvbWJvc19saXN0JFZJQlJBTlRbaV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIgPSBjb21ib3NfbGlzdCRWU1tpXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlcjIgPSBjb21ib3NfbGlzdCRWUzJbaV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV90dW5pbmdfdmlyYWwgPSBjb21ib3NfbGlzdCR0dW5lX3ZpcmFsW2ldLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX25vdF92aXJhbCA9IGNvbWJvc19saXN0JHR1bmVfbm90X3ZpcmFsW2ldLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfZGVlcHZpcmZpbmRlciA9IGNvbWJvc19saXN0JERWRltpXSkKICAKICBpZiAobWF4KHZpcmFsX3Njb3Jlc1ssaV0pPD0wKSB7CiAgICBudW1fdmlydXNlcyRudW1fdmlydXNlc1tpXSA8LSAwCiAgfQogIGVsc2UgewogICAgbnVtX3ZpcnVzZXMkbnVtX3ZpcnVzZXNbaV0gPC0gdGFibGUodmlyYWxfc2NvcmVzWyxpXT49MSlbWzJdXQogIH0KICAKICBudW1fdmlydXNlcyR0b29sY29tYm9baV0gPC0gY29tYm9zX2xpc3QkdG9vbGNvbWJvW2ldCiAgCiAgbnVtX3ZpcnVzZXMkdG9vbGNvbWJvMltpXSA8LSBjb21ib3NfbGlzdCR0b29sY29tYm8yW2ldCn0KCm51bV92aXJ1c2VzJG51bXJ1bGVzIDwtIHN0cl9jb3VudChudW1fdmlydXNlcyR0b29sY29tYm8sICIxIikKbnVtX3ZpcnVzZXMgPC0gbnVtX3ZpcnVzZXNbb3JkZXIobnVtX3ZpcnVzZXMkbnVtX3ZpcnVzZXMsIGRlY3JlYXNpbmc9RiksXQpudW1fdmlydXNlcyR0b29sY29tYm8gPC0gZmFjdG9yKG51bV92aXJ1c2VzJHRvb2xjb21ibywgbGV2ZWxzID0gdW5pcXVlKG51bV92aXJ1c2VzJHRvb2xjb21ibykpCm51bV92aXJ1c2VzJHRvb2xjb21ibzIgPC0gZmFjdG9yKG51bV92aXJ1c2VzJHRvb2xjb21ibzIsIGxldmVscyA9IHVuaXF1ZShudW1fdmlydXNlcyR0b29sY29tYm8yKSkKbnVtX3ZpcnVzZXMkbnVtcnVsZXMgPC0gYXMuZmFjdG9yKG51bV92aXJ1c2VzJG51bXJ1bGVzKQpgYGAKCgoKYGBge3J9CnZpcmFsX3Njb3Jlc19ub3plcm9zIDwtIHZpcmFsX3Njb3Jlc1tyb3dTdW1zKHZpcmFsX3Njb3Jlcyk+MCxdCnZpcmFsX3Njb3Jlc19ub3plcm9zIDwtIHZpcmFsX3Njb3Jlc19ub3plcm9zICsgMQp2aXJhbF9zY29yZXNfbm96ZXJvcyA8LSBhcy5kYXRhLmZyYW1lKHZpcmFsX3Njb3Jlc19ub3plcm9zKQoKY29sbmFtZXModmlyYWxfc2NvcmVzX25vemVyb3MpIDwtIG51bV92aXJ1c2VzJHRvb2xjb21ibwpgYGAKCmBgYHtyfQpsaWJyYXJ5KHBoeWxvc2VxKQpgYGAKCgpgYGB7cn0KdG9vbGRhdGEgPC0gbnVtX3ZpcnVzZXMKCnJvd25hbWVzKHRvb2xkYXRhKSA8LSB0b29sZGF0YSR0b29sY29tYm8KYGBgCgpgYGB7cn0KcGh5c2VxX3Bvb2xlZCA8LSBwaHlsb3NlcShvdHVfdGFibGUodmlyYWxfc2NvcmVzX25vemVyb3MsIHRheGFfYXJlX3Jvd3MgPSBUKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZV9kYXRhKHRvb2xkYXRhKSkKYGBgCgpgYGB7cn0Kb3JkaW5hdGlvbiA8LSBwaHlsb3NlcTo6b3JkaW5hdGUocGh5c2VxID1waHlzZXFfcG9vbGVkLCBtZXRob2QgPSAiUENvQSIsIGRpc3RhbmNlID0gImJyYXkiKQpwaHlsb3NlcTo6cGxvdF9vcmRpbmF0aW9uKHBoeXNlcSA9IHBoeXNlcV9wb29sZWQsIG9yZGluYXRpb24gPSBvcmRpbmF0aW9uLAogICAgICAgICAgICAgICAgICAgICAgICAgIHNoYXBlPSJudW1ydWxlcyIsIGNvbG9yPSJudW1fdmlydXNlcyIpICsgCiAgZ2VvbV9wb2ludChzaXplID0gMykgKwogIHRoZW1lX2J3KCkgKwogIGdlb21fbGFiZWwobGFiZWw9dG9vbGRhdGEkdG9vbGNvbWJvKQoKcGh5bG9zZXE6OnBsb3Rfb3JkaW5hdGlvbihwaHlzZXEgPSBwaHlzZXFfcG9vbGVkLCBvcmRpbmF0aW9uID0gb3JkaW5hdGlvbiwKICAgICAgICAgICAgICAgICAgICAgICAgICBzaGFwZT0ibnVtcnVsZXMiLCBjb2xvcj0ibnVtX3ZpcnVzZXMiKSArIAogIGdlb21fcG9pbnQoc2l6ZSA9IDMpICsKICB0aGVtZV9idygpCmBgYAoKYGBge3J9CmJyYXlfZGlzdCA8LSBwaHlsb3NlcTo6ZGlzdGFuY2UocGh5c2VxX3Bvb2xlZCwgbWV0aG9kPSJicmF5IikKY2x1c3RlcnMgPC0gaGNsdXN0KGRpc3QoYnJheV9kaXN0KSkKcGxvdChjbHVzdGVycykKCm15Y2x1c3RlcnMgPC0gY3V0cmVlKGNsdXN0ZXJzLCBoPTAuOCkKYGBgCgoKYGBge3J9CiNuYW1lcyhteWNsdXN0ZXJzW215Y2x1c3RlcnM9PTFdKQojbmFtZXMobXljbHVzdGVyc1tteWNsdXN0ZXJzPT0yXSkKI25hbWVzKG15Y2x1c3RlcnNbbXljbHVzdGVycz09M10pCiNuYW1lcyhteWNsdXN0ZXJzW215Y2x1c3RlcnM9PTRdKQojbmFtZXMobXljbHVzdGVyc1tteWNsdXN0ZXJzPT01XSkKCm15Y2x1c3RlcnNfZGYgPC0gdGliYmxlKGNvbWJvPW5hbWVzKG15Y2x1c3RlcnMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9pbmRleD1teWNsdXN0ZXJzKQoKbXljbHVzdGVyc19kZiA8LSBzZXBhcmF0ZShteWNsdXN0ZXJzX2RmLCBjb2w9Y29tYm8sIGludG89YygidG52IiwgIkRWRiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0diIsICJWQiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJWUyIsICJWUzIiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBzZXA9IiAiLCByZW1vdmUgPSBGKQoKCnRvb2xfY291bnQgPC0gYXMuZGF0YS5mcmFtZShyYmluZCh0YWJsZShteWNsdXN0ZXJzX2RmJHRudiwgbXljbHVzdGVyc19kZiRjbHVzdGVyX2luZGV4KVsyLF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgdGFibGUobXljbHVzdGVyc19kZiREVkYsIG15Y2x1c3RlcnNfZGYkY2x1c3Rlcl9pbmRleClbMixdLAogICAgICAgICAgICAgICAgICAgICAgICAgIHRhYmxlKG15Y2x1c3RlcnNfZGYkdHYsIG15Y2x1c3RlcnNfZGYkY2x1c3Rlcl9pbmRleClbMixdLAogICAgICAgICAgICAgICAgICAgICAgICAgIHRhYmxlKG15Y2x1c3RlcnNfZGYkVkIsIG15Y2x1c3RlcnNfZGYkY2x1c3Rlcl9pbmRleClbMixdLAogICAgICAgICAgICAgICAgICAgICAgICAgIHRhYmxlKG15Y2x1c3RlcnNfZGYkVlMsIG15Y2x1c3RlcnNfZGYkY2x1c3Rlcl9pbmRleClbMixdLAogICAgICAgICAgICAgICAgICAgICAgICAgIHRhYmxlKG15Y2x1c3RlcnNfZGYkVlMyLCBteWNsdXN0ZXJzX2RmJGNsdXN0ZXJfaW5kZXgpWzIsXSkKICAgICAgICAgICAgICAgICAgICApCgp0b29sX2NvdW50IDwtIGRhdGEuZnJhbWUodChhcHBseSh0b29sX2NvdW50LCBjKDEpLCBmdW5jdGlvbih4KSB7eCA8LSB4L3RhYmxlKG15Y2x1c3RlcnNfZGYkY2x1c3Rlcl9pbmRleCl9KSkpCgoKCnRvb2xfY291bnQkbWV0aG9kIDwtIGMoInRudiIsICJEVkYiLCAidHYiLCAiVkIiLCAiVlMiLCAiVlMyIikKCnRvb2xfY291bnQgPC0gbWVsdCh0b29sX2NvdW50KQoKY29sbmFtZXModG9vbF9jb3VudCkgPC0gYygidG9vbCIsICJjbHVzdGVyX2luZGV4IiwgInRvb2xfY291bnRfbm9ybSIpCmBgYAoKYGBge3J9CnBhbCA8LSBnZ3RoZW1lczo6dGFibGVhdV9jb2xvcl9wYWwocGFsZXR0ZT0iVGFibGVhdSAxMCIsIHR5cGU9InJlZ3VsYXIiKQoKZ2dwbG90KHRvb2xfY291bnQsIGFlcyh4PXRvb2wsIHk9dG9vbF9jb3VudF9ub3JtLAogICAgICAgICAgICAgICAgICAgZmlsbD10b29sLAogICAgICAgICAgICAgICAgICAgY29sb3I9dG9vbCkpICsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0LCBhbmdsZSA9IDkwKSwKICAgICNsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDYpKSwgMC41KSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDYpKSwgMSkpICsKICB4bGFiKCJUb29sIikgKwogIHlsYWIoIlByb3BvcnRpb24gb2YgVGltZXMgaW4gQ2x1c3RlciIpICsgCiAgZmFjZXRfd3JhcCh+Y2x1c3Rlcl9pbmRleCwgbnJvdz0xKQpgYGAKCmBgYHtyfQphY2N1cmFjeV9zY29yZXNfbWVsdCA8LSBhY2N1cmFjeV9zY29yZXMgJT4lIAogIHNlbGVjdChwcmVjaXNpb24sIHJlY2FsbCwgTUNDLCBudW1ydWxlcywgdG9vbGNvbWJvKSAlPiUKICBncm91cF9ieShudW1ydWxlcywgdG9vbGNvbWJvKSAlPiUKICBzdW1tYXJpc2UocHJlY2lzaW9uPW1lYW4ocHJlY2lzaW9uKSwKICAgICAgICAgICAgcmVjYWxsPW1lYW4ocmVjYWxsKSwKICAgICAgICAgICAgTUNDPW1lYW4oTUNDKSkgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHM9YyhwcmVjaXNpb24sIHJlY2FsbCwgTUNDKSwgCiAgICAgICAgICAgICAgIG5hbWVzX3RvPSJwZXJmb3JtYW5jZV9tZXRyaWMiLAogICAgICAgICAgICAgICB2YWx1ZXNfdG89InBlcmZvcm1hbmNlX21ldHJpY19zY29yZSIpCmBgYAoKCmBgYHtyfQpteWNsdXN0ZXJzX2RmIDwtIGlubmVyX2pvaW4oYWNjdXJhY3lfc2NvcmVzX21lbHQsIG15Y2x1c3RlcnNfZGYsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgYnk9YygidG9vbGNvbWJvIj0iY29tYm8iKSkKCm15Y2x1c3RlcnNfZGYkY2x1c3Rlcl9pbmRleCA8LSBhcy5mYWN0b3IobXljbHVzdGVyc19kZiRjbHVzdGVyX2luZGV4KQpgYGAKCmBgYHtyfQpnZ3Bsb3QobXljbHVzdGVyc19kZiwgYWVzKHg9Y2x1c3Rlcl9pbmRleCwgeT1wZXJmb3JtYW5jZV9tZXRyaWNfc2NvcmUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3I9Y2x1c3Rlcl9pbmRleCwgZmlsbD1jbHVzdGVyX2luZGV4KSkgKwogIGdlb21fYm94cGxvdCgpICsKICBnZW9tX3BvaW50KGFscGhhPTAuNSkgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCwgYW5nbGUgPSA5MCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICB5bGFiKCJNQ0MiKSArCiAgeGxhYigiQ2x1c3RlciIpICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDkpKSwgMC41KSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDkpKSwgMSkpICsKICBmYWNldF93cmFwKH5wZXJmb3JtYW5jZV9tZXRyaWMpCmBgYAoKIyMgYWxsIDYgdG9vbHMgZXhhbXBsZQpgYGB7cn0KdmlydXNlcyRrZWVwX3Njb3JlX2FsbCA8LSBnZXR0aW5nX3ZpcmFsX3NldF8xKHZpcnVzZXMsIGluY2x1ZGVfZGVlcHZpcmZpbmRlciA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpYnJhbnQgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIyID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX3ZpcmFsID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX25vdF92aXJhbCA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlciA9IFQpCmBgYAoKCmBgYHtyfQp2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfYWxsIDwtICJ0cnVlIG5lZ2F0aXZlIgp2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfYWxsW3ZpcnVzZXMkc2VxdHlwZT09InZpcnVzIiAmIHZpcnVzZXMka2VlcF9zY29yZV9hbGw8MV0gPC0gImZhbHNlIG5lZ2F0aXZlIgp2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfYWxsW3ZpcnVzZXMkc2VxdHlwZT09InZpcnVzIiAmIHZpcnVzZXMka2VlcF9zY29yZV9hbGw+PTFdIDwtICJ0cnVlIHBvc2l0aXZlIgp2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfYWxsW3ZpcnVzZXMkc2VxdHlwZSE9InZpcnVzIiAmIHZpcnVzZXMka2VlcF9zY29yZV9hbGw+PTFdIDwtICJmYWxzZSBwb3NpdGl2ZSIKYGBgCgpgYGB7cn0KVFAgPC0gdGFibGUodmlydXNlcyRjb25mdXNpb25fbWF0cml4X2FsbClbNF0KRlAgPC0gdGFibGUodmlydXNlcyRjb25mdXNpb25fbWF0cml4X2FsbClbMl0KVE4gPC0gdGFibGUodmlydXNlcyRjb25mdXNpb25fbWF0cml4X2FsbClbM10KRk4gPC0gdGFibGUodmlydXNlcyRjb25mdXNpb25fbWF0cml4X2FsbClbMV0KCnByZWNpc2lvbiA8LSBUUC8oVFArRlApCnByZWNpc2lvbgpyZWNhbGwgPC0gVFAvKFRQK0ZOKQpyZWNhbGwKRjEgPC0gMipwcmVjaXNpb24qcmVjYWxsLyhwcmVjaXNpb24rcmVjYWxsKQpGMQoKTUNDIDwtIChUUCpUTi1GUCpGTikvc3FydChhcy5udW1lcmljKFRQK0ZQKSphcy5udW1lcmljKFRQK0ZOKSphcy5udW1lcmljKFROK0ZQKSphcy5udW1lcmljKFROK0ZOKSkKTUNDCmBgYApwcmVjaXNpb249NjIlLCByZWNhbGw9OTIlLCBNQ0M9NzMlCgoKdmlzdWFsaXppbmcgY29uZnVzaW9uIG1hdHJpeCBieSB0YXhhCmBgYHtyfQpjb25mdXNpb25fYnlfdGF4YSA8LSBtZWx0KHRhYmxlKHZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9hbGwsIHZpcnVzZXMkc2VxdHlwZSwgdmlydXNlcyRJbmRleCkpCmNvbG5hbWVzKGNvbmZ1c2lvbl9ieV90YXhhKSA8LSBjKCJjb25mdXNpb25fbWF0cml4IiwgInNlcXR5cGUiLCJJbmRleCIsICJjb3VudCIpCmBgYAoKYGBge3J9CnRhYmxlKHZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9hbGwpCgpsZW5ndGgoZ3JlcCgidHJ1ZSIsIHZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9hbGwpKS9ucm93KHZpcnVzZXMpCmBgYAoKYGBge3J9Cmxlbmd0aChncmVwKCJ0cnVlIHBvc2l0aXZlIiwgdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2FsbCkpL2xlbmd0aChncmVwKCJ2aXJ1cyIsIHZpcnVzZXMkc2VxdHlwZSkpCmBgYAoKYGBge3J9CnBhbCA8LSBnZ3RoZW1lczo6dGFibGVhdV9jb2xvcl9wYWwocGFsZXR0ZT0iVGFibGVhdSAxMCIsIHR5cGU9InJlZ3VsYXIiKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoY29uZnVzaW9uX2J5X3RheGEsIGFlcyh4PWNvdW50LCB5PWFzLmZhY3RvcihJbmRleCksCiAgICAgICAgICAgICAgICAgICBmaWxsPWNvbmZ1c2lvbl9tYXRyaXgsCiAgICAgICAgICAgICAgICAgICBjb2xvcj1jb25mdXNpb25fbWF0cml4KSkgKwogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDQpKSwgMC41KSwKICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiZmFsc2UgbmVnYXRpdmUiLCAiZmFsc2UgcG9zaXRpdmUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidHJ1ZSBuZWdhdGl2ZSIsICJ0cnVlIHBvc2l0aXZlIikpICsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg0KSksIDEpLAogICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJmYWxzZSBuZWdhdGl2ZSIsICJmYWxzZSBwb3NpdGl2ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0cnVlIG5lZ2F0aXZlIiwgInRydWUgcG9zaXRpdmUiKSkgKwogIHhsYWIoIk51bWJlciBvZiBTZXF1ZW5jZXMiKSArCiAgeWxhYigiIikgKyAKICBmYWNldF93cmFwKH5zZXF0eXBlLCBzY2FsZXMgPSAiZnJlZSIpICsKICBjb29yZF9mbGlwKCkKYGBgCgoKIyMgaGlnaCByZWNhbGwgZXhhbXBsZQpgYGB7cn0KdmlydXNlcyRrZWVwX3Njb3JlX2hpZ2hfcmVjYWxsIDwtIGdldHRpbmdfdmlyYWxfc2V0XzEodmlydXNlcywgaW5jbHVkZV9kZWVwdmlyZmluZGVyID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlicmFudCA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlcjIgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV90dW5pbmdfdmlyYWwgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV90dW5pbmdfbm90X3ZpcmFsID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyID0gVCkKYGBgCgoKYGBge3J9CnZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9oaWdoX3JlY2FsbCA8LSAidHJ1ZSBuZWdhdGl2ZSIKdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2hpZ2hfcmVjYWxsW3ZpcnVzZXMkc2VxdHlwZT09InZpcnVzIiAmIHZpcnVzZXMka2VlcF9zY29yZV9oaWdoX3JlY2FsbDwxXSA8LSAiZmFsc2UgbmVnYXRpdmUiCnZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9oaWdoX3JlY2FsbFt2aXJ1c2VzJHNlcXR5cGU9PSJ2aXJ1cyIgJiB2aXJ1c2VzJGtlZXBfc2NvcmVfaGlnaF9yZWNhbGw+PTFdIDwtICJ0cnVlIHBvc2l0aXZlIgp2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9yZWNhbGxbdmlydXNlcyRzZXF0eXBlIT0idmlydXMiICYgdmlydXNlcyRrZWVwX3Njb3JlX2hpZ2hfcmVjYWxsPj0xXSA8LSAiZmFsc2UgcG9zaXRpdmUiCmBgYAoKCnZpc3VhbGl6aW5nIGNvbmZ1c2lvbiBtYXRyaXggYnkgdGF4YQpgYGB7cn0KY29uZnVzaW9uX2J5X3RheGEgPC0gbWVsdCh0YWJsZSh2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9yZWNhbGwsIHZpcnVzZXMkc2VxdHlwZSwgdmlydXNlcyRJbmRleCkpCmNvbG5hbWVzKGNvbmZ1c2lvbl9ieV90YXhhKSA8LSBjKCJjb25mdXNpb25fbWF0cml4IiwgInNlcXR5cGUiLCJJbmRleCIsICJjb3VudCIpCmBgYAoKCgpgYGB7cn0KcGFsIDwtIGdndGhlbWVzOjp0YWJsZWF1X2NvbG9yX3BhbChwYWxldHRlPSJUYWJsZWF1IDEwIiwgdHlwZT0icmVndWxhciIpCmBgYAoKYGBge3J9CnAyIDwtIGdncGxvdChjb25mdXNpb25fYnlfdGF4YSwgYWVzKHg9Y291bnQsIHk9YXMuZmFjdG9yKEluZGV4KSwKICAgICAgICAgICAgICAgICAgIGZpbGw9Y29uZnVzaW9uX21hdHJpeCwKICAgICAgICAgICAgICAgICAgIGNvbG9yPWNvbmZ1c2lvbl9tYXRyaXgpKSArCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNCkpLCAwLjUpLAogICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJmYWxzZSBuZWdhdGl2ZSIsICJmYWxzZSBwb3NpdGl2ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0cnVlIG5lZ2F0aXZlIiwgInRydWUgcG9zaXRpdmUiKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDQpKSwgMSksCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoImZhbHNlIG5lZ2F0aXZlIiwgImZhbHNlIHBvc2l0aXZlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRydWUgbmVnYXRpdmUiLCAidHJ1ZSBwb3NpdGl2ZSIpKSArCiAgeGxhYigiTnVtYmVyIG9mIFNlcXVlbmNlcyIpICsKICB5bGFiKCIiKSArIAogIGZhY2V0X3dyYXAofnNlcXR5cGUsIHNjYWxlcyA9ICJmcmVlIikgKwogIGNvb3JkX2ZsaXAoKQpwMgpgYGAKCmFjY3VyYWN5OgpgYGB7cn0KbGVuZ3RoKGdyZXAoInRydWUiLCB2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9yZWNhbGwpKS9ucm93KHZpcnVzZXMpCmBgYAowLjg4NwoKcmVjYWxsCmBgYHtyfQpsZW5ndGgoZ3JlcCgidHJ1ZSBwb3NpdGl2ZSIsIHZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9oaWdoX3JlY2FsbCkpL2xlbmd0aChncmVwKCJ2aXJ1cyIsIHZpcnVzZXMkc2VxdHlwZSkpCmBgYApyZWNvdmVyIGFsbW9zdCBhbGwgb2YgdGhlIHZpcnVzZXMgdGhpcyB3YXksIGJ1dCBtb3JlIHByb3Rpc3QgY29udGFtaW5hdGlvbgoKMC45NjAKCmBgYHtyfQpjb25mdXNpb25fYnlfdGF4YSA8LSB2aXJ1c2VzICU+JSBjb3VudChjb25mdXNpb25fbWF0cml4X2hpZ2hfcmVjYWxsLCBzZXF0eXBlLCBzaXplX2NsYXNzKQoKY29sbmFtZXMoY29uZnVzaW9uX2J5X3RheGEpIDwtIGMoImNvbmZ1c2lvbl9tYXRyaXgiLCAic2VxdHlwZSIsInNpemUiLCAiY291bnQiKQpgYGAKCgoKIyMgZmV3IHRvb2xzLCBoaWdoIE1DQyBleGFtcGxlCmBgYHtyfQp2aXJ1c2VzJGtlZXBfc2NvcmVfZmV3X3Rvb2xzIDwtIGdldHRpbmdfdmlyYWxfc2V0XzEodmlydXNlcywgaW5jbHVkZV9kZWVwdmlyZmluZGVyID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlicmFudCA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlcjIgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV90dW5pbmdfdmlyYWwgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV90dW5pbmdfbm90X3ZpcmFsID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyID0gRikKYGBgCgoKYGBge3J9CnZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9mZXdfdG9vbHMgPC0gInRydWUgbmVnYXRpdmUiCnZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9mZXdfdG9vbHNbdmlydXNlcyRzZXF0eXBlPT0idmlydXMiICYgdmlydXNlcyRrZWVwX3Njb3JlX2Zld190b29sczwxXSA8LSAiZmFsc2UgbmVnYXRpdmUiCnZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9mZXdfdG9vbHNbdmlydXNlcyRzZXF0eXBlPT0idmlydXMiICYgdmlydXNlcyRrZWVwX3Njb3JlX2Zld190b29scz49MV0gPC0gInRydWUgcG9zaXRpdmUiCnZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9mZXdfdG9vbHNbdmlydXNlcyRzZXF0eXBlIT0idmlydXMiICYgdmlydXNlcyRrZWVwX3Njb3JlX2Zld190b29scz49MV0gPC0gImZhbHNlIHBvc2l0aXZlIgpgYGAKCmBgYHtyfQpUUCA8LSB0YWJsZSh2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfZmV3X3Rvb2xzKVs0XQpGUCA8LSB0YWJsZSh2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfZmV3X3Rvb2xzKVsyXQpUTiA8LSB0YWJsZSh2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfZmV3X3Rvb2xzKVszXQpGTiA8LSB0YWJsZSh2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfZmV3X3Rvb2xzKVsxXQoKcHJlY2lzaW9uIDwtIFRQLyhUUCtGUCkKcHJlY2lzaW9uCnJlY2FsbCA8LSBUUC8oVFArRk4pCnJlY2FsbApGMSA8LSAyKnByZWNpc2lvbipyZWNhbGwvKHByZWNpc2lvbityZWNhbGwpCkYxCgpNQ0MgPC0gKFRQKlROLUZQKkZOKS9zcXJ0KGFzLm51bWVyaWMoVFArRlApKmFzLm51bWVyaWMoVFArRk4pKmFzLm51bWVyaWMoVE4rRlApKmFzLm51bWVyaWMoVE4rRk4pKQpNQ0MKYGBgCnByZWNpc2lvbj03NyUsIHJlY2FsbD03NiUsIE1DQz03NCUKCgojIyBWaXN1YWxpemluZyB0aGUgZGlmZmVyZW50IHNldHMKCmBgYHtyfQpjb25mdXNpb25fYnlfdGF4YV9tZXRob2QgPC0gdmlydXNlcyAlPiUgCiAgc2VsZWN0KGNvbnRhaW5zKCJjb25mdXNpb25fbWF0cml4IiksIHNlcXR5cGUsIEluZGV4KSAlPiUKICBwaXZvdF9sb25nZXIoY29scz1jb250YWlucygiY29uZnVzaW9uX21hdHJpeCIpLCAKICAgICAgICAgICAgICAgbmFtZXNfdG89ImNvbmZ1c2lvbl9tYXRyaXhfdHlwZSIsCiAgICAgICAgICAgICAgIHZhbHVlc190bz0iY29uZnVzaW9uX21hdHJpeF92YWx1ZSIpICU+JQogIGNvdW50KHNlcXR5cGUsIEluZGV4LCBjb25mdXNpb25fbWF0cml4X3R5cGUsIGNvbmZ1c2lvbl9tYXRyaXhfdmFsdWUpCmBgYAoKYGBge3J9CnBhbCA8LSBnZ3RoZW1lczo6dGFibGVhdV9jb2xvcl9wYWwocGFsZXR0ZT0iVGFibGVhdSAxMCIsIHR5cGU9InJlZ3VsYXIiKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoY29uZnVzaW9uX2J5X3RheGFfbWV0aG9kLCBhZXMoeT1uLCB4PWNvbmZ1c2lvbl9tYXRyaXhfdHlwZSwKICAgICAgICAgICAgICAgICAgIGZpbGw9Y29uZnVzaW9uX21hdHJpeF92YWx1ZSwKICAgICAgICAgICAgICAgICAgIGNvbG9yPWNvbmZ1c2lvbl9tYXRyaXhfdmFsdWUpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDQpKSwgMC41KSwKICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiZmFsc2UgbmVnYXRpdmUiLCAiZmFsc2UgcG9zaXRpdmUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidHJ1ZSBuZWdhdGl2ZSIsICJ0cnVlIHBvc2l0aXZlIikpICsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg0KSksIDEpLAogICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJmYWxzZSBuZWdhdGl2ZSIsICJmYWxzZSBwb3NpdGl2ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0cnVlIG5lZ2F0aXZlIiwgInRydWUgcG9zaXRpdmUiKSkgKwogIHhsYWIoIk51bWJlciBvZiBTZXF1ZW5jZXMiKSArCiAgeWxhYigiIikgKyAKICBmYWNldF93cmFwKH5zZXF0eXBlLCBzY2FsZXMgPSAiZnJlZSIpCmBgYAoKYGBge3J9CmNvbmZ1c2lvbl9ieV90YXhhX21ldGhvZCA8LSB2aXJ1c2VzICU+JSAKICBzZWxlY3QoY29udGFpbnMoImNvbmZ1c2lvbl9tYXRyaXgiKSwgc2VxdHlwZSwgSW5kZXgpICU+JQogIHBpdm90X2xvbmdlcihjb2xzPWNvbnRhaW5zKCJjb25mdXNpb25fbWF0cml4IiksIAogICAgICAgICAgICAgICBuYW1lc190bz0iY29uZnVzaW9uX21hdHJpeF90eXBlIiwKICAgICAgICAgICAgICAgdmFsdWVzX3RvPSJjb25mdXNpb25fbWF0cml4X3ZhbHVlIikgJT4lCiAgY291bnQoc2VxdHlwZSwgSW5kZXgsIGNvbmZ1c2lvbl9tYXRyaXhfdHlwZSwgY29uZnVzaW9uX21hdHJpeF92YWx1ZSkgJT4lCiAgZmlsdGVyKGdyZXBsKCJ0cnVlIiwgY29uZnVzaW9uX21hdHJpeF92YWx1ZSkpICU+JQogIG11dGF0ZShjb25mdXNpb25fbWF0cml4X3R5cGU9c3ViKCJjb25mdXNpb25fbWF0cml4XyIsICIiLCBjb25mdXNpb25fbWF0cml4X3R5cGUpKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoY29uZnVzaW9uX2J5X3RheGFfbWV0aG9kLCBhZXMoeT1uLCB4PWNvbmZ1c2lvbl9tYXRyaXhfdHlwZSwKICAgICAgICAgICAgICAgICAgIGZpbGw9Y29uZnVzaW9uX21hdHJpeF92YWx1ZSwKICAgICAgICAgICAgICAgICAgIGNvbG9yPWNvbmZ1c2lvbl9tYXRyaXhfdmFsdWUpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xMCwgYW5nbGU9OTApLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg0KSlbMzo0XSwgMC41KSwKICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YyggCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRydWUgbmVnYXRpdmUiLCAidHJ1ZSBwb3NpdGl2ZSIpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNCkpWzM6NF0sIDEpLAogICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0cnVlIG5lZ2F0aXZlIiwgInRydWUgcG9zaXRpdmUiKSkgKwogIHhsYWIoIlRvb2wgU2V0IikgKwogIHlsYWIoIkNvbnRpZyBDb3VudCIpICsgCiAgZmFjZXRfd3JhcCh+c2VxdHlwZSwgc2NhbGVzID0gImZyZWUiKQpgYGAKCiMjIGFub3RoZXIgd2F5IG9mIHZpc3VhbGl6aW5nIHRoZSBkaWZmZXJlbnQgdG9vbCBzZXRzIHNjb3JlcwpgYGB7cn0KdmlydXNlcyR0cnVlX3ZpcnVzIDwtICJub3QiCnZpcnVzZXMkdHJ1ZV92aXJ1c1t2aXJ1c2VzJHNlcXR5cGU9PSJ2aXJ1cyJdIDwtICJ2aXJ1cyIKCnZpcnVzZXNfbG9uZ19zY29yZXMgPC0gdmlydXNlcyAlPiUgCiAgc2VsZWN0KGNvbnRhaW5zKCJrZWVwX3Njb3JlX2hpZ2giKSwgY29udGFpbnMoImtlZXBfc2NvcmVfYWxsIiksIHNpemVfY2xhc3MsIHRydWVfdmlydXMpICU+JQogIHBpdm90X2xvbmdlcihjb2xzPWNvbnRhaW5zKCJrZWVwX3Njb3JlXyIpLCAKICAgICAgICAgICAgICAgbmFtZXNfdG89InJ1bGVfY29tYmluYXRpb24iLAogICAgICAgICAgICAgICB2YWx1ZXNfdG89InZpcmFsX3Njb3JlIikgJT4lIAogIG11dGF0ZSh2aXJhbF9zY29yZT1hcy5mYWN0b3Iocm91bmQodmlyYWxfc2NvcmUpKSkgJT4lCiAgZ3JvdXBfYnkocnVsZV9jb21iaW5hdGlvbiwgdmlyYWxfc2NvcmUsIHNpemVfY2xhc3MsIHRydWVfdmlydXMpICU+JQogIHN1bW1hcmlzZShuID0gbigpKQoKdmlydXNlc19sb25nX3Njb3JlcyRzaXplX2NsYXNzIDwtIGZhY3Rvcih2aXJ1c2VzX2xvbmdfc2NvcmVzJHNpemVfY2xhc3MsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoIjMtNWtiIiwgIjUtMTBrYiIsICI+MTBrYiIpKQpgYGAKCmBgYHtyfQpnZ3Bsb3QodmlydXNlc19sb25nX3Njb3JlcywgYWVzKHk9biwgeD1ydWxlX2NvbWJpbmF0aW9uLAogICAgICAgICAgICAgICAgICAgZmlsbD12aXJhbF9zY29yZSkpICsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsKICB0aGVtZV9saWdodCgpICsKICBjb29yZF9mbGlwKCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIKICApICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlB1T3IiLCApICsKICB4bGFiKCIiKSArCiAgeWxhYigiTnVtYmVyIG9mIFNlcXVlbmNlcyIpICsgCiAgZmFjZXRfZ3JpZCh+dHJ1ZV92aXJ1cywgc2NhbGVzID0gImZyZWUiKQpgYGAKCmBgYHtyfQpnZ3Bsb3QodmlydXNlc19sb25nX3Njb3JlcywgYWVzKHk9biwgeD1ydWxlX2NvbWJpbmF0aW9uLAogICAgICAgICAgICAgICAgICAgZmlsbD12aXJhbF9zY29yZSkpICsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsKICB0aGVtZV9saWdodCgpICsKICBjb29yZF9mbGlwKCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIKICApICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlB1T3IiLCApICsKICB4bGFiKCIiKSArCiAgeWxhYigiTnVtYmVyIG9mIFNlcXVlbmNlcyIpICsgCiAgZmFjZXRfZ3JpZChzaXplX2NsYXNzfnRydWVfdmlydXMsIHNjYWxlcyA9ICJmcmVlIikKYGBgCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwpFeHRyYSBTdHVmZgojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKCmBgYHtyfQpnZ3Bsb3QodmlydXNlcywgYWVzKHg9Y2hlY2t2X2xlbmd0aCwgeT1rZWVwX3Njb3JlX2hpZ2hfTUNDLAogICAgICAgICAgICAgICAgICAgZmlsbD1jb25mdXNpb25fbWF0cml4X2hpZ2hfTUNDLAogICAgICAgICAgICAgICAgICAgY29sb3I9Y29uZnVzaW9uX21hdHJpeF9oaWdoX01DQykpICsKICBnZW9tX3BvaW50KHN0YXQ9ImlkZW50aXR5Iiwgc2hhcGU9MjEpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg0KSksIDAuNSksCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoImZhbHNlIG5lZ2F0aXZlIiwgImZhbHNlIHBvc2l0aXZlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRydWUgbmVnYXRpdmUiLCAidHJ1ZSBwb3NpdGl2ZSIpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNCkpLCAxKSwKICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiZmFsc2UgbmVnYXRpdmUiLCAiZmFsc2UgcG9zaXRpdmUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidHJ1ZSBuZWdhdGl2ZSIsICJ0cnVlIHBvc2l0aXZlIikpICsKICB4bGFiKCJTZXF1ZW5jZSBMZW5ndGggKGJwKSIpICsKICB5bGFiKCJQaXBlbGluZSBWaXJhbCBTY29yZSIpICsgCiAgZmFjZXRfd3JhcCh+c2VxdHlwZSkgKyAKICBzY2FsZV94X2xvZzEwKCkKYGBgCgoKYGBge3J9CmdncGxvdCh2aXJ1c2VzLCBhZXMoeD1jaGVja3ZfY29tcGxldGVuZXNzLCB5PWhhbGxtYXJrLAogICAgICAgICAgICAgICAgICAgZmlsbD1jb25mdXNpb25fbWF0cml4X2hpZ2hfcmVjYWxsLAogICAgICAgICAgICAgICAgICAgY29sb3I9Y29uZnVzaW9uX21hdHJpeF9oaWdoX3JlY2FsbCkpICsKICBnZW9tX3BvaW50KHN0YXQ9ImlkZW50aXR5Iiwgc2hhcGU9MjEpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg0KSksIDAuNSksCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoImZhbHNlIG5lZ2F0aXZlIiwgImZhbHNlIHBvc2l0aXZlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRydWUgbmVnYXRpdmUiLCAidHJ1ZSBwb3NpdGl2ZSIpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNCkpLCAxKSwKICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiZmFsc2UgbmVnYXRpdmUiLCAiZmFsc2UgcG9zaXRpdmUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidHJ1ZSBuZWdhdGl2ZSIsICJ0cnVlIHBvc2l0aXZlIikpICsKICB4bGFiKCJDaGVja1YgQ29tcGxldGVuZXNzIikgKwogIHlsYWIoIk51bWJlciBvZiBIYWxsbWFyayBHZW5lcyIpICsgCiAgZmFjZXRfd3JhcCh+c2VxdHlwZSkgKyAKICBzY2FsZV94X2xvZzEwKCkKYGBgCgpgYGB7cn0KZ2dwbG90KHZpcnVzZXMsIGFlcyh4PWNoZWNrdl9jb21wbGV0ZW5lc3MsIHk9a2VlcF9zY29yZV9oaWdoX01DQywKICAgICAgICAgICAgICAgICAgIGZpbGw9Y29uZnVzaW9uX21hdHJpeF9oaWdoX3JlY2FsbCwKICAgICAgICAgICAgICAgICAgIGNvbG9yPWNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9yZWNhbGwpKSArCiAgZ2VvbV9wb2ludChzdGF0PSJpZGVudGl0eSIsIHNoYXBlPTIxKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNCkpLCAwLjUpLAogICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJmYWxzZSBuZWdhdGl2ZSIsICJmYWxzZSBwb3NpdGl2ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0cnVlIG5lZ2F0aXZlIiwgInRydWUgcG9zaXRpdmUiKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDQpKSwgMSksCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoImZhbHNlIG5lZ2F0aXZlIiwgImZhbHNlIHBvc2l0aXZlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRydWUgbmVnYXRpdmUiLCAidHJ1ZSBwb3NpdGl2ZSIpKSArCiAgeGxhYigiQ2hlY2tWIENvbXBsZXRlbmVzcyIpICsKICB5bGFiKCJQaXBlbGluZSBWaXJhbCBTY29yZSIpICsgCiAgZmFjZXRfd3JhcCh+c2VxdHlwZSkgKyAKICBzY2FsZV94X2xvZzEwKCkKYGBgCgpgYGB7cn0KZ2dwbG90KHZpcnVzZXMsIGFlcyh4PWNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9yZWNhbGwsIHk9Y2hlY2t2X2xlbmd0aCwKICAgICAgICAgICAgICAgICAgIGZpbGw9Y29uZnVzaW9uX21hdHJpeF9oaWdoX3JlY2FsbCwKICAgICAgICAgICAgICAgICAgIGNvbG9yPWNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9yZWNhbGwpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDQpKSwgMC41KSwKICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiZmFsc2UgbmVnYXRpdmUiLCAiZmFsc2UgcG9zaXRpdmUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidHJ1ZSBuZWdhdGl2ZSIsICJ0cnVlIHBvc2l0aXZlIikpICsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg0KSksIDEpLAogICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJmYWxzZSBuZWdhdGl2ZSIsICJmYWxzZSBwb3NpdGl2ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0cnVlIG5lZ2F0aXZlIiwgInRydWUgcG9zaXRpdmUiKSkgKwogIHhsYWIoIlNlcXVlbmNlIExlbmd0aCAoYnApIikgKwogIHlsYWIoIlBpcGVsaW5lIFZpcmFsIFNjb3JlIikgKwogIHNjYWxlX3lfbG9nMTAoKQpgYGAKCgoKbG9va2luZyBhdCBmYWxzZSBuZWdhdGl2ZXMKYGBge3J9CnZpcnVzZXNfZmFsc2VfbmVncyA8LSB2aXJ1c2VzWyh2aXJ1c2VzJHNlcXR5cGU9PSJ2aXJ1cyIgJiB2aXJ1c2VzJGtlZXBfc2NvcmVfaGlnaF9yZWNhbGw8MSksXQpgYGAKCmxvb2tpbmcgYXQgcHJvdGlzdHMgY2FsbGluZyB2aXJhbApgYGB7cn0KdmlydXNlc19mYWxzZV9wb3NfcHJvdGlzdHMgPC0gdmlydXNlc1sodmlydXNlcyRzZXF0eXBlPT0icHJvdGlzdCIgJiB2aXJ1c2VzJGtlZXBfc2NvcmVfaGlnaF9yZWNhbGw+PTEpLF0KYGBgCgoKCgoKCgoKCgoKIyBDb25zaWRlcmluZyBob3cgZWFjaCBtZXRob2QgY29udHJpYnV0ZXMgdG8gdGhlIGZpbmFsIHByZWRpY3Rpb24gKGhpZ2ggTUNDKQoKYGBge3J9CnZpcnVzZXMka2VlcF9zY29yZV92YiA8LSBnZXR0aW5nX3ZpcmFsX3NldF8xKHZpcnVzZXMsIGluY2x1ZGVfZGVlcHZpcmZpbmRlciA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpYnJhbnQgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIyID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX3ZpcmFsID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX25vdF92aXJhbCA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlciA9IEYpCgp2aXJ1c2VzJGtlZXBfc2NvcmVfdmJfdHYgPC0gZ2V0dGluZ192aXJhbF9zZXRfMSh2aXJ1c2VzLCBpbmNsdWRlX2RlZXB2aXJmaW5kZXIgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aWJyYW50ID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyMiA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ192aXJhbCA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ19ub3RfdmlyYWwgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIgPSBGKQpgYGAKCmBgYHtyfQp2aXJ1c2VzX2hpZ2ggPC0gdmlydXNlc1t2aXJ1c2VzJGtlZXBfc2NvcmVfdmJfdHY+PTEsXSAjdW5jb21tZW50IHRoaXMgbGluZSBpZiB3YW50IHRvIHVzZSBhbGwgNiB0b29scwp2aXJ1c2VzX2hpZ2hfbW9kIDwtIHZpcnVzZXNfaGlnaCAlPiUgc2VsZWN0KGtlZXBfc2NvcmVfdmIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtlZXBfc2NvcmVfdmJfdHYpCiN2aXJ1c2VzX2hpZ2hfbW9kIDwtIGFwcGx5KHZpcnVzZXNfaGlnaF9tb2QsIGMoMSwyKSwgZnVuY3Rpb24oeCkge2lmICh4ID49IDEpIHt4IDwtIDF9IGVsc2Uge3ggPC0gMH19KQp2aXJ1c2VzX2hpZ2hfbW9kIDwtIGFzX3RpYmJsZSh2aXJ1c2VzX2hpZ2hfbW9kKQoKCmBgYAoKYGBge3J9CnNtX20gPC0gcmVzaGFwZTI6Om1lbHQodmlydXNlc19oaWdoX21vZCkKY29sbmFtZXMoc21fbSkgPC0gYygibWV0aG9kIiwgInNjb3JlIikKYGBgCgpgYGB7cn0KZ2dwbG90KHNtX20sIGFlcyh4PW1ldGhvZCwgeT1zY29yZSwKICAgICAgICAgICAgICAgICAgIGZpbGw9YXMuZmFjdG9yKHNjb3JlKSkpICsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsKICB0aGVtZV9saWdodCgpICsKICBjb29yZF9mbGlwKCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIKICApICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lID0gJ051bWJlciBvZiBNZXRob2RzJywKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEoYyh2aXJpZGlzKDE0KSksIDEpKSArCiAgeGxhYigiUHJpbWFyeSBNZXRob2QiKSArCiAgeWxhYigiQ291bnQgb2YgVmlyYWwgQ29udGlncyIpICsKICBjb29yZF9mbGlwKCkKYGBgCgoKCgojIFJPQyAKYGBge3J9CmxpYnJhcnkocFJPQykKYGBgCgpgYGB7cn0KdmlydXNlcyR0cnVlcG9zaXRpdmUgPC0gcmVwKDAsIG5yb3codmlydXNlcykpCnZpcnVzZXMkdHJ1ZXBvc2l0aXZlW3ZpcnVzZXMkc2VxdHlwZT09InZpcnVzIl0gPC0gMQpgYGAKCgpgYGB7cn0Kcm9jb2JqIDwtIHJvYyh2aXJ1c2VzJHRydWVwb3NpdGl2ZSwgdmlydXNlcyRrZWVwX3Njb3JlKQpyb2NvYmpfYWxsIDwtIHJvYyh2aXJ1c2VzJHRydWVwb3NpdGl2ZSwgdmlydXNlcyRrZWVwX3Njb3JlX2FsbCkKYXVjIDwtIHJvdW5kKGF1Yyh2aXJ1c2VzJHRydWVwb3NpdGl2ZSwgdmlydXNlcyRrZWVwX3Njb3JlKSw0KQphdWNfYWxsIDwtIHJvdW5kKGF1Yyh2aXJ1c2VzJHRydWVwb3NpdGl2ZSwgdmlydXNlcyRrZWVwX3Njb3JlX2FsbCksNCkKI2NyZWF0ZSBST0MgcGxvdApnZ3JvYyhyb2NvYmosIGNvbG91ciA9ICdzdGVlbGJsdWUnLCBzaXplID0gMikgKwogIGdndGl0bGUocGFzdGUwKCdST0MgQ3VydmUgJywgJyhBVUMgPSAnLCBhdWMsICcpJykpICsKICBjb29yZF9lcXVhbCgpCmdncm9jKHJvY29ial9hbGwsIGNvbG91ciA9ICdncmVlbicsIHNpemUgPSAyKSArCiAgZ2d0aXRsZShwYXN0ZTAoJ1JPQyBDdXJ2ZSAnLCAnKEFVQyA9ICcsIGF1Y19hbGwsICcpJykpCmBgYApTZW5zaXRpdml0eTogVGhlIHByb2JhYmlsaXR5IHRoYXQgdGhlIG1vZGVsIHByZWRpY3RzIGEgcG9zaXRpdmUgb3V0Y29tZSBmb3IgYW4gb2JzZXJ2YXRpb24gd2hlbiBpbmRlZWQgdGhlIG91dGNvbWUgaXMgcG9zaXRpdmUuClNwZWNpZmljaXR5OiBUaGUgcHJvYmFiaWxpdHkgdGhhdCB0aGUgbW9kZWwgcHJlZGljdHMgYSBuZWdhdGl2ZSBvdXRjb21lIGZvciBhbiBvYnNlcnZhdGlvbiB3aGVuIGluZGVlZCB0aGUgb3V0Y29tZSBpcyBuZWdhdGl2ZS4KCgoKCiMgQ29tcGFyaW5nIGJlaGF2aW9yIG9mIGFsbCB0ZXN0aW5nIHNldHMgY29tYmluZWQgKGNsdXN0ZXJpbmcgYW5hbHlzZXMpCgpgYGB7cn0KdmlyYWxfc2NvcmVzIDwtIG1hdHJpeChkYXRhPTAsIG5yb3c9bnJvdyh2aXJ1c2VzKSwgbmNvbD1ucm93KGNvbWJvc19saXN0KSkKbnVtX3ZpcnVzZXMgPC0gZGF0YS5mcmFtZSh0b29sY29tYm89cmVwKDAsIG5yb3coY29tYm9zX2xpc3QpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBudW1fdmlydXNlcz1yZXAoMCwgbnJvdyhjb21ib3NfbGlzdCkpKQoKZm9yIChpIGluIDE6bnJvdyhjb21ib3NfbGlzdCkpIHsKICB2aXJhbF9zY29yZXNbLGldIDwtIGdldHRpbmdfdmlyYWxfc2V0XzEodmlydXNlcywgaW5jbHVkZV92aWJyYW50ID0gY29tYm9zX2xpc3QkVklCUkFOVFtpXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlciA9IGNvbWJvc19saXN0JFZTW2ldLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyMiA9IGNvbWJvc19saXN0JFZTMltpXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZyA9IGNvbWJvc19saXN0JENoZWNrVltpXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX2thaWp1ID0gY29tYm9zX2xpc3QkS2FpanVbaV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV9kZWVwdmlyZmluZGVyID0gY29tYm9zX2xpc3QkRFZGW2ldKQogIAogIG51bV92aXJ1c2VzJG51bV92aXJ1c2VzW2ldIDwtIHRhYmxlKHZpcmFsX3Njb3Jlc1ssaV0+PTEpW1syXV0KICAKICBudW1fdmlydXNlcyR0b29sY29tYm9baV0gPC0gY29tYm9zX2xpc3QkdG9vbGNvbWJvW2ldCiAgCiAgbnVtX3ZpcnVzZXMkdG9vbGNvbWJvMltpXSA8LSBjb21ib3NfbGlzdCR0b29sY29tYm8yW2ldCn0KCm51bV92aXJ1c2VzJG51bXJ1bGVzIDwtIHN0cl9jb3VudChudW1fdmlydXNlcyR0b29sY29tYm8sICIxIikKbnVtX3ZpcnVzZXMgPC0gbnVtX3ZpcnVzZXNbb3JkZXIobnVtX3ZpcnVzZXMkbnVtX3ZpcnVzZXMsIGRlY3JlYXNpbmc9RiksXQpudW1fdmlydXNlcyR0b29sY29tYm8gPC0gZmFjdG9yKG51bV92aXJ1c2VzJHRvb2xjb21ibywgbGV2ZWxzID0gdW5pcXVlKG51bV92aXJ1c2VzJHRvb2xjb21ibykpCm51bV92aXJ1c2VzJHRvb2xjb21ibzIgPC0gZmFjdG9yKG51bV92aXJ1c2VzJHRvb2xjb21ibzIsIGxldmVscyA9IHVuaXF1ZShudW1fdmlydXNlcyR0b29sY29tYm8yKSkKbnVtX3ZpcnVzZXMkbnVtcnVsZXMgPC0gYXMuZmFjdG9yKG51bV92aXJ1c2VzJG51bXJ1bGVzKQpgYGAKCgpgYGB7cn0KZ2dwbG90KG51bV92aXJ1c2VzLCBhZXMoeD10b29sY29tYm8sIHk9bnVtX3ZpcnVzZXMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3I9bnVtcnVsZXMsIGZpbGw9bnVtcnVsZXMpKSArCiAgZ2VvbV9wb2ludCgpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQsIGFuZ2xlID0gOTApLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgeGxhYigiVG9vbCBDb21iaW5hdGlvbiAoQ1YsIERWRiwgS0osIFZCLCBWUywgVlMyKSIpICsKICB5bGFiKCJOdW0gVmlydXNlcyBQcmVkaWN0ZWQiKQoKZ2dwbG90KG51bV92aXJ1c2VzLCBhZXMoeD10b29sY29tYm8yLCB5PW51bV92aXJ1c2VzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yPW51bXJ1bGVzLCBmaWxsPW51bXJ1bGVzKSkgKwogIGdlb21fcG9pbnQoKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0LCBhbmdsZSA9IDkwKSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHhsYWIoIlRvb2wgQ29tYmluYXRpb24gKENWLCBEVkYsIEtKLCBWQiwgVlMsIFZTMikiKSArCiAgeWxhYigiTnVtIFZpcnVzZXMgUHJlZGljdGVkIikKYGBgCgpgYGB7cn0KZ2dwbG90KG51bV92aXJ1c2VzLCBhZXMoeD1udW1ydWxlcywgeT1udW1fdmlydXNlcykpICsKICBnZW9tX2JveHBsb3QoYWVzKGNvbG9yPW51bXJ1bGVzKSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yPW51bXJ1bGVzLCBmaWxsPW51bXJ1bGVzKSkgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCwgYW5nbGUgPSA5MCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICB4bGFiKCJOdW1iZXIgb2YgVG9vbHMiKSArCiAgeWxhYigiTnVtIFZpcnVzZXMgUHJlZGljdGVkIikKYGBgCgoKYGBge3J9CnZpcmFsX3Njb3Jlc19ub3plcm9zIDwtIHZpcmFsX3Njb3Jlc1tyb3dTdW1zKHZpcmFsX3Njb3Jlcyk+MCxdCnZpcmFsX3Njb3Jlc19ub3plcm9zIDwtIHZpcmFsX3Njb3Jlc19ub3plcm9zICsgMQp2aXJhbF9zY29yZXNfbm96ZXJvcyA8LSBhcy5kYXRhLmZyYW1lKHZpcmFsX3Njb3Jlc19ub3plcm9zKQoKY29sbmFtZXModmlyYWxfc2NvcmVzX25vemVyb3MpIDwtIG51bV92aXJ1c2VzJHRvb2xjb21ibzIKYGBgCgpgYGB7cn0KbGlicmFyeShwaHlsb3NlcSkKYGBgCgoKYGBge3J9CnRvb2xkYXRhIDwtIG51bV92aXJ1c2VzCgpyb3duYW1lcyh0b29sZGF0YSkgPC0gdG9vbGRhdGEkdG9vbGNvbWJvMgpgYGAKCmBgYHtyfQpwaHlzZXFfcG9vbGVkIDwtIHBoeWxvc2VxKG90dV90YWJsZSh2aXJhbF9zY29yZXNfbm96ZXJvcywgdGF4YV9hcmVfcm93cyA9IFQpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX2RhdGEodG9vbGRhdGEpKQpgYGAKCmBgYHtyfQpvcmRpbmF0aW9uIDwtIHBoeWxvc2VxOjpvcmRpbmF0ZShwaHlzZXEgPXBoeXNlcV9wb29sZWQsIG1ldGhvZCA9ICJQQ29BIiwgZGlzdGFuY2UgPSAiYnJheSIpCnBoeWxvc2VxOjpwbG90X29yZGluYXRpb24ocGh5c2VxID0gcGh5c2VxX3Bvb2xlZCwgb3JkaW5hdGlvbiA9IG9yZGluYXRpb24sCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2hhcGU9Im51bXJ1bGVzIiwgY29sb3I9Im51bV92aXJ1c2VzIikgKyAKICBnZW9tX3BvaW50KHNpemUgPSAzKSArCiAgdGhlbWVfYncoKSArCiAgZ2VvbV9sYWJlbChsYWJlbD10b29sZGF0YSR0b29sY29tYm8pCgpwaHlsb3NlcTo6cGxvdF9vcmRpbmF0aW9uKHBoeXNlcSA9IHBoeXNlcV9wb29sZWQsIG9yZGluYXRpb24gPSBvcmRpbmF0aW9uLAogICAgICAgICAgICAgICAgICAgICAgICAgIHNoYXBlPSJudW1ydWxlcyIsIGNvbG9yPSJudW1fdmlydXNlcyIpICsgCiAgZ2VvbV9wb2ludChzaXplID0gMykgKwogIHRoZW1lX2J3KCkKYGBgCnRvIGRvOiB0cnkgY29sb3JpbmcgYWJvdmUgYmFzZWQgb24gdGhlIEYxIHNjb3JlcyBvZiB0aGUgdGVzdGluZyBzZXQgb24gZWFjaCBjb21iaW5hdGlvbgoKYGBge3J9CmJyYXlfZGlzdCA8LSBwaHlsb3NlcTo6ZGlzdGFuY2UocGh5c2VxX3Bvb2xlZCwgbWV0aG9kPSJicmF5IikKY2x1c3RlcnMgPC0gaGNsdXN0KGRpc3QoYnJheV9kaXN0KSkKcGxvdChjbHVzdGVycykKCm15Y2x1c3RlcnMgPC0gY3V0cmVlKGNsdXN0ZXJzLCBoPTEuMSkKYGBgCgoKYGBge3J9Cm5hbWVzKG15Y2x1c3RlcnNbbXljbHVzdGVycz09MV0pCm5hbWVzKG15Y2x1c3RlcnNbbXljbHVzdGVycz09Ml0pCm5hbWVzKG15Y2x1c3RlcnNbbXljbHVzdGVycz09M10pCm5hbWVzKG15Y2x1c3RlcnNbbXljbHVzdGVycz09NF0pCm5hbWVzKG15Y2x1c3RlcnNbbXljbHVzdGVycz09NV0pCgpteWNsdXN0ZXJzX2RmIDwtIHRpYmJsZShjb21ibz1uYW1lcyhteWNsdXN0ZXJzKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsdXN0ZXJfaW5kZXg9bXljbHVzdGVycykKCm15Y2x1c3RlcnNfZGYgPC0gc2VwYXJhdGUobXljbHVzdGVyc19kZiwgY29sPWNvbWJvLCBpbnRvPWMoIkNoZWNrViIsICJEVkYiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiS2FpanUiLCAiVklCUkFOVCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJWaXJTb3J0ZXIiLCAiVmlyU29ydGVyMiIpLAogICAgICAgICAgICAgICAgICAgICAgICAgIHNlcD0iICIsIHJlbW92ZSA9IEYpCgoKdG9vbF9jb3VudCA8LSBhcy5kYXRhLmZyYW1lKHJiaW5kKHRhYmxlKG15Y2x1c3RlcnNfZGYkQ2hlY2tWLCBteWNsdXN0ZXJzX2RmJGNsdXN0ZXJfaW5kZXgpWzIsXSwKICAgICAgICAgICAgICAgICAgICAgICAgICB0YWJsZShteWNsdXN0ZXJzX2RmJERWRiwgbXljbHVzdGVyc19kZiRjbHVzdGVyX2luZGV4KVsyLF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgdGFibGUobXljbHVzdGVyc19kZiRLYWlqdSwgbXljbHVzdGVyc19kZiRjbHVzdGVyX2luZGV4KVsyLF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgdGFibGUobXljbHVzdGVyc19kZiRWSUJSQU5ULCBteWNsdXN0ZXJzX2RmJGNsdXN0ZXJfaW5kZXgpWzIsXSwKICAgICAgICAgICAgICAgICAgICAgICAgICB0YWJsZShteWNsdXN0ZXJzX2RmJFZpclNvcnRlciwgbXljbHVzdGVyc19kZiRjbHVzdGVyX2luZGV4KVsyLF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgdGFibGUobXljbHVzdGVyc19kZiRWaXJTb3J0ZXIyLCBteWNsdXN0ZXJzX2RmJGNsdXN0ZXJfaW5kZXgpWzIsXSkKICAgICAgICAgICAgICAgICAgICApCgp0b29sX2NvdW50JG1ldGhvZCA8LSBjKCJDaGVja1YiLCAiRFZGIiwgIkthaWp1IiwgIlZJQlJBTlQiLCAiVmlyU29ydGVyIiwgIlZpclNvcnRlcjIiKQoKdG9vbF9jb3VudCA8LSBtZWx0KHRvb2xfY291bnQpCgpjb2xuYW1lcyh0b29sX2NvdW50KSA8LSBjKCJ0b29sIiwgImNsdXN0ZXJfaW5kZXgiLCAidG9vbF9jb3VudCIpCmBgYAoKYGBge3J9CnBhbCA8LSBnZ3RoZW1lczo6dGFibGVhdV9jb2xvcl9wYWwocGFsZXR0ZT0iVGFibGVhdSAxMCIsIHR5cGU9InJlZ3VsYXIiKQoKZ2dwbG90KHRvb2xfY291bnQsIGFlcyh4PWNsdXN0ZXJfaW5kZXgsIHk9dG9vbF9jb3VudCwKICAgICAgICAgICAgICAgICAgIGZpbGw9Y2x1c3Rlcl9pbmRleCwKICAgICAgICAgICAgICAgICAgIGNvbG9yPWNsdXN0ZXJfaW5kZXgpKSArCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNikpLCAwLjUpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNikpLCAxKSkgKwogIHhsYWIoIkNsdXN0ZXIiKSArCiAgeWxhYigiTnVtYmVyIG9mIFRpbWVzIGluIENsdXN0ZXIiKSArIAogIGZhY2V0X3dyYXAofnRvb2wsIHNjYWxlcyA9ICJmcmVlIikKYGBgCgoKYGBge3J9CiBnZ3Bsb3QodmlydXNlcywgYWVzKHg9Y2hlY2t2X3ZpcmFsX2dlbmVzLCB5PWNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9wcmVjaXNpb24sCiAgICAgICAgICAgICAgICAgICBmaWxsPWNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9wcmVjaXNpb24sCiAgICAgICAgICAgICAgICAgICBjb2xvcj1jb25mdXNpb25fbWF0cml4X2hpZ2hfcHJlY2lzaW9uKSkgKwogIGdlb21fYm94cGxvdChhbHBoYT0wLjMpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg0KSksIDAuNSksCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoImZhbHNlIG5lZ2F0aXZlIiwgImZhbHNlIHBvc2l0aXZlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRydWUgbmVnYXRpdmUiLCAidHJ1ZSBwb3NpdGl2ZSIpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNCkpLCAxKSwKICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiZmFsc2UgbmVnYXRpdmUiLCAiZmFsc2UgcG9zaXRpdmUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidHJ1ZSBuZWdhdGl2ZSIsICJ0cnVlIHBvc2l0aXZlIikpICsKICB4bGFiKCJOdW1iZXIgb2YgVmlyYWwgU2VxdWVuY2VzIikgKwogIHlsYWIoIiIpICsgCiAgZmFjZXRfd3JhcCh+c2VxdHlwZSwgc2NhbGVzID0gImZyZWUiKSArCiAgY29vcmRfZmxpcCgpCgogZ2dwbG90KHZpcnVzZXMsIGFlcyh4PXBlcmNlbnRfdmlyYWwsIHk9Y29uZnVzaW9uX21hdHJpeF9oaWdoX3ByZWNpc2lvbiwKICAgICAgICAgICAgICAgICAgIGZpbGw9Y29uZnVzaW9uX21hdHJpeF9oaWdoX3ByZWNpc2lvbiwKICAgICAgICAgICAgICAgICAgIGNvbG9yPWNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9wcmVjaXNpb24pKSArCiAgZ2VvbV9ib3hwbG90KGFscGhhPTAuMykgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDQpKSwgMC41KSwKICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiZmFsc2UgbmVnYXRpdmUiLCAiZmFsc2UgcG9zaXRpdmUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidHJ1ZSBuZWdhdGl2ZSIsICJ0cnVlIHBvc2l0aXZlIikpICsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg0KSksIDEpLAogICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJmYWxzZSBuZWdhdGl2ZSIsICJmYWxzZSBwb3NpdGl2ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0cnVlIG5lZ2F0aXZlIiwgInRydWUgcG9zaXRpdmUiKSkgKwogIHhsYWIoIlBlcmNlbnQgR2VuZXMgVmlyYWwiKSArCiAgeWxhYigiIikgKyAKICBmYWNldF93cmFwKH5zZXF0eXBlLCBzY2FsZXMgPSAiZnJlZSIpICsKICBjb29yZF9mbGlwKCkKCiBnZ3Bsb3QodmlydXNlcywgYWVzKHg9aGFsbG1hcmssIHk9Y29uZnVzaW9uX21hdHJpeF9oaWdoX3ByZWNpc2lvbiwKICAgICAgICAgICAgICAgICAgIGZpbGw9Y29uZnVzaW9uX21hdHJpeF9oaWdoX3ByZWNpc2lvbiwKICAgICAgICAgICAgICAgICAgIGNvbG9yPWNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9wcmVjaXNpb24pKSArCiAgZ2VvbV9ib3hwbG90KGFscGhhPTAuMykgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDQpKSwgMC41KSwKICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiZmFsc2UgbmVnYXRpdmUiLCAiZmFsc2UgcG9zaXRpdmUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidHJ1ZSBuZWdhdGl2ZSIsICJ0cnVlIHBvc2l0aXZlIikpICsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg0KSksIDEpLAogICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJmYWxzZSBuZWdhdGl2ZSIsICJmYWxzZSBwb3NpdGl2ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0cnVlIG5lZ2F0aXZlIiwgInRydWUgcG9zaXRpdmUiKSkgKwogIHhsYWIoIk51bWJlciBvZiBIYWxsbWFyayBHZW5lcyIpICsKICB5bGFiKCIiKSArIAogIGZhY2V0X3dyYXAofnNlcXR5cGUsIHNjYWxlcyA9ICJmcmVlIikgKwogIGNvb3JkX2ZsaXAoKQogCmdncGxvdCh2aXJ1c2VzLCBhZXMoeD1oYWxsbWFyaywgeT1jaGVja3ZfdmlyYWxfZ2VuZXMsCiAgICAgICAgICAgICAgICAgICBmaWxsPWNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9wcmVjaXNpb24sCiAgICAgICAgICAgICAgICAgICBjb2xvcj1jb25mdXNpb25fbWF0cml4X2hpZ2hfcHJlY2lzaW9uKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC4zKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNCkpLCAwLjUpLAogICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJmYWxzZSBuZWdhdGl2ZSIsICJmYWxzZSBwb3NpdGl2ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0cnVlIG5lZ2F0aXZlIiwgInRydWUgcG9zaXRpdmUiKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDQpKSwgMSksCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoImZhbHNlIG5lZ2F0aXZlIiwgImZhbHNlIHBvc2l0aXZlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRydWUgbmVnYXRpdmUiLCAidHJ1ZSBwb3NpdGl2ZSIpKSArCiAgeGxhYigiTnVtYmVyIG9mIEhhbGxtYXJrIEdlbmVzIikgKwogIHlsYWIoIk51bWJlciBvZiBWaXJhbCBHZW5lcyIpICsgCiAgZmFjZXRfd3JhcCh+c2VxdHlwZSwgc2NhbGVzID0gImZyZWUiKSArCiAgY29vcmRfZmxpcCgpCmBgYAoKYGBge3J9CnZpcnVzZXNfZmFsc2VfcG9zaXRpdmUgPC0gdmlydXNlc1t2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9wcmVjaXNpb249PSJmYWxzZSBwb3NpdGl2ZSIsXQp2aXJ1c2VzX2ZhbHNlX25lZ2F0aXZlIDwtIHZpcnVzZXNbdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2hpZ2hfcHJlY2lzaW9uPT0iZmFsc2UgbmVnYXRpdmUiLF0KYGBgCgpgYGB7cn0KZ2dwbG90KHZpcnVzZXMsIGFlcyh4PWhhbGxtYXJrLCB5PWNoZWNrdl92aXJhbF9nZW5lcywKICAgICAgICAgICAgICAgICAgIGZpbGw9Y2hlY2t2X2xlbmd0aCwKICAgICAgICAgICAgICAgICAgIGNvbG9yPWNoZWNrdl9sZW5ndGgsCiAgICAgICAgICAgICAgICAgICBzaGFwZT1jaGVja3ZfcHJvdmlydXMpKSArCiAgZ2VvbV9wb2ludChhbHBoYT0wLjMpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgeGxhYigiTnVtYmVyIG9mIEhhbGxtYXJrIEdlbmVzIikgKwogIHlsYWIoIk51bWJlciBvZiBWaXJhbCBHZW5lcyIpICsgCiAgZmFjZXRfd3JhcCh+c2VxdHlwZSwgc2NhbGVzID0gImZyZWUiKSArCiAgY29vcmRfZmxpcCgpCgpnZ3Bsb3QodmlydXNlc19mYWxzZV9wb3NpdGl2ZSwgYWVzKHg9aGFsbG1hcmssIHk9Y2hlY2t2X2xlbmd0aCwKICAgICAgICAgICAgICAgICAgIGZpbGw9Y2hlY2t2X3ZpcmFsX2dlbmVzLAogICAgICAgICAgICAgICAgICAgY29sb3I9Y2hlY2t2X3ZpcmFsX2dlbmVzLAogICAgICAgICAgICAgICAgICAgc2hhcGU9Y2hlY2t2X3Byb3ZpcnVzKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC4zKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHhsYWIoIk51bWJlciBvZiBIYWxsbWFyayBHZW5lcyIpICsKICB5bGFiKCJDb250aWcgTGVuZ3RoIikgKyAKICBmYWNldF93cmFwKH5zZXF0eXBlLCBzY2FsZXMgPSAiZnJlZSIpICsKICBjb29yZF9mbGlwKCkKCmdncGxvdCh2aXJ1c2VzX2ZhbHNlX3Bvc2l0aXZlW3ZpcnVzZXNfZmFsc2VfcG9zaXRpdmUkc2VxdHlwZT09ImJhY3RlcmlhIl0sIGFlcyh4PWhhbGxtYXJrLCB5PWNoZWNrdl9sZW5ndGgsCiAgICAgICAgICAgICAgICAgICBmaWxsPWNoZWNrdl92aXJhbF9nZW5lcywKICAgICAgICAgICAgICAgICAgIGNvbG9yPWNoZWNrdl92aXJhbF9nZW5lcywKICAgICAgICAgICAgICAgICAgIHNoYXBlPWNoZWNrdl9wcm92aXJ1cykpICsKICBnZW9tX3BvaW50KGFscGhhPTAuMykgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICB4bGFiKCJOdW1iZXIgb2YgSGFsbG1hcmsgR2VuZXMiKSArCiAgeWxhYigiQ29udGlnIExlbmd0aCIpICsgCiAgZmFjZXRfd3JhcCh+S2FpanVfVmlyYWwsIHNjYWxlcyA9ICJmcmVlIikgKwogIGNvb3JkX2ZsaXAoKQoKZ2dwbG90KHZpcnVzZXNfZmFsc2VfcG9zaXRpdmVbdmlydXNlc19mYWxzZV9wb3NpdGl2ZSRzZXF0eXBlPT0iZnVuZ2kiXSwgYWVzKHg9aGFsbG1hcmssIHk9Y2hlY2t2X2xlbmd0aCwKICAgICAgICAgICAgICAgICAgIGZpbGw9a2VlcF9zY29yZV9oaWdoX3ByZWNpc2lvbiwKICAgICAgICAgICAgICAgICAgIGNvbG9yPWtlZXBfc2NvcmVfaGlnaF9wcmVjaXNpb24sCiAgICAgICAgICAgICAgICAgICBzaGFwZT1jaGVja3ZfcHJvdmlydXMpKSArCiAgZ2VvbV9wb2ludChhbHBoYT0wLjMpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgeGxhYigiTnVtYmVyIG9mIEhhbGxtYXJrIEdlbmVzIikgKwogIHlsYWIoIkNvbnRpZyBMZW5ndGgiKSArIAogIGZhY2V0X3dyYXAofkthaWp1X1ZpcmFsLCBzY2FsZXMgPSAiZnJlZSIpICsKICBjb29yZF9mbGlwKCkKCmdncGxvdCh2aXJ1c2VzX2ZhbHNlX3Bvc2l0aXZlW3ZpcnVzZXNfZmFsc2VfcG9zaXRpdmUkc2VxdHlwZT09InByb3Rpc3QiXSwgYWVzKHg9aGFsbG1hcmssIHk9Y2hlY2t2X2xlbmd0aCwKICAgICAgICAgICAgICAgICAgIGZpbGw9Y2hlY2t2X3ZpcmFsX2dlbmVzLAogICAgICAgICAgICAgICAgICAgY29sb3I9Y2hlY2t2X3ZpcmFsX2dlbmVzLAogICAgICAgICAgICAgICAgICAgc2hhcGU9Y2hlY2t2X3Byb3ZpcnVzKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC4zKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHhsYWIoIk51bWJlciBvZiBIYWxsbWFyayBHZW5lcyIpICsKICB5bGFiKCJDb250aWcgTGVuZ3RoIikgKyAKICBmYWNldF93cmFwKH5LYWlqdV9WaXJhbCwgc2NhbGVzID0gImZyZWUiKSArCiAgY29vcmRfZmxpcCgpCgpnZ3Bsb3QodmlydXNlc19mYWxzZV9uZWdhdGl2ZSwgYWVzKHg9aGFsbG1hcmssIHk9Y2hlY2t2X2xlbmd0aCwKICAgICAgICAgICAgICAgICAgIGZpbGw9Y2hlY2t2X3ZpcmFsX2dlbmVzLAogICAgICAgICAgICAgICAgICAgY29sb3I9Y2hlY2t2X3ZpcmFsX2dlbmVzLAogICAgICAgICAgICAgICAgICAgc2hhcGU9Y2hlY2t2X3Byb3ZpcnVzKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC4zKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHhsYWIoIk51bWJlciBvZiBIYWxsbWFyayBHZW5lcyIpICsKICB5bGFiKCJDb250aWcgTGVuZ3RoIikgKyAKICBmYWNldF93cmFwKH5LYWlqdV9WaXJhbCwgc2NhbGVzID0gImZyZWUiKSArCiAgY29vcmRfZmxpcCgpCgpnZ3Bsb3QodmlydXNlc19mYWxzZV9uZWdhdGl2ZSwgYWVzKHg9aGFsbG1hcmssIHk9Y2hlY2t2X2xlbmd0aCwKICAgICAgICAgICAgICAgICAgIGZpbGw9a2VlcF9zY29yZV9oaWdoX3ByZWNpc2lvbiwKICAgICAgICAgICAgICAgICAgIGNvbG9yPWtlZXBfc2NvcmVfaGlnaF9wcmVjaXNpb24sCiAgICAgICAgICAgICAgICAgICBzaGFwZT1jaGVja3ZfcHJvdmlydXMpKSArCiAgZ2VvbV9wb2ludChhbHBoYT0wLjMpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgeGxhYigiTnVtYmVyIG9mIEhhbGxtYXJrIEdlbmVzIikgKwogIHlsYWIoIkNvbnRpZyBMZW5ndGgiKSArIAogIGZhY2V0X3dyYXAofkthaWp1X1ZpcmFsLCBzY2FsZXMgPSAiZnJlZSIpICsKICBjb29yZF9mbGlwKCkKYGBgCgoKCgpgYGB7cn0KdGFibGUodmlydXNlcyRoYWxsbWFya1t2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9wcmVjaXNpb249PSJmYWxzZSBwb3NpdGl2ZSJdPjApCgp0YWJsZSh2aXJ1c2VzJHBlcmNlbnRfaG9zdFt2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9wcmVjaXNpb249PSJmYWxzZSBwb3NpdGl2ZSJdPDUwKQpgYGAKCgojIyBWaXN1YWxpemluZyBjb25mdXNpb24gbWF0cml4IGJ5IG51bWJlciBvZiB0b29scwoKYGBge3J9CmNvbmZ1c2lvbl9ieV9rZWVwX3Njb3JlIDwtIHZpcnVzZXMgJT4lIAogIHNlbGVjdChjb250YWlucygia2VlcF9zY29yZSIpLCBzZXF0eXBlLCBJbmRleCkgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHM9Y29udGFpbnMoImtlZXBfc2NvcmUiKSwgCiAgICAgICAgICAgICAgIG5hbWVzX3RvPSJwaXBlbGluZV90eXBlIiwKICAgICAgICAgICAgICAgdmFsdWVzX3RvPSJwaXBlbGluZV92YWx1ZSIpICU+JQogIG11dGF0ZShwaXBlbGluZV90eXBlPXN1Yigia2VlcF9zY29yZV8iLCAiIiwgcGlwZWxpbmVfdHlwZSkpICU+JQogIGNvdW50KHNlcXR5cGUsIEluZGV4LCBwaXBlbGluZV90eXBlLCBwaXBlbGluZV92YWx1ZSkKYGBgCgpgYGB7cn0KY29uZnVzaW9uX2J5X2tlZXBfc2NvcmUkY29uZnVzaW9uX21hdHJpeCA8LSAidHJ1ZSBuZWdhdGl2ZSIKY29uZnVzaW9uX2J5X2tlZXBfc2NvcmUkY29uZnVzaW9uX21hdHJpeFtjb25mdXNpb25fYnlfa2VlcF9zY29yZSRzZXF0eXBlPT0idmlydXMiICYgY29uZnVzaW9uX2J5X2tlZXBfc2NvcmUkcGlwZWxpbmVfdmFsdWU8MV0gPC0gImZhbHNlIG5lZ2F0aXZlIgpjb25mdXNpb25fYnlfa2VlcF9zY29yZSRjb25mdXNpb25fbWF0cml4W2NvbmZ1c2lvbl9ieV9rZWVwX3Njb3JlJHNlcXR5cGU9PSJ2aXJ1cyIgJiBjb25mdXNpb25fYnlfa2VlcF9zY29yZSRwaXBlbGluZV92YWx1ZT49MV0gPC0gInRydWUgcG9zaXRpdmUiCmNvbmZ1c2lvbl9ieV9rZWVwX3Njb3JlJGNvbmZ1c2lvbl9tYXRyaXhbY29uZnVzaW9uX2J5X2tlZXBfc2NvcmUkc2VxdHlwZSE9InZpcnVzIiAmIGNvbmZ1c2lvbl9ieV9rZWVwX3Njb3JlJHBpcGVsaW5lX3ZhbHVlPj0xXSA8LSAiZmFsc2UgcG9zaXRpdmUiCmBgYAoKYGBge3J9CmNvbmZ1c2lvbl9ieV9rZWVwX3Njb3JlJGtlZXBfc2NvcmVfdmlzdWFsaXplIDwtIGNvbmZ1c2lvbl9ieV9rZWVwX3Njb3JlJHBpcGVsaW5lX3ZhbHVlCmNvbmZ1c2lvbl9ieV9rZWVwX3Njb3JlJGtlZXBfc2NvcmVfdmlzdWFsaXplW2NvbmZ1c2lvbl9ieV9rZWVwX3Njb3JlJHBpcGVsaW5lX3ZhbHVlPjFdIDwtICI+IDEiCmNvbmZ1c2lvbl9ieV9rZWVwX3Njb3JlJGtlZXBfc2NvcmVfdmlzdWFsaXplW2NvbmZ1c2lvbl9ieV9rZWVwX3Njb3JlJHBpcGVsaW5lX3ZhbHVlPT0xXSA8LSAiMSIKY29uZnVzaW9uX2J5X2tlZXBfc2NvcmUka2VlcF9zY29yZV92aXN1YWxpemVbY29uZnVzaW9uX2J5X2tlZXBfc2NvcmUkcGlwZWxpbmVfdmFsdWU9PTAuNV0gPC0gIjAuNSIKY29uZnVzaW9uX2J5X2tlZXBfc2NvcmUka2VlcF9zY29yZV92aXN1YWxpemVbY29uZnVzaW9uX2J5X2tlZXBfc2NvcmUkcGlwZWxpbmVfdmFsdWU9PTBdIDwtICIwIgpjb25mdXNpb25fYnlfa2VlcF9zY29yZSRrZWVwX3Njb3JlX3Zpc3VhbGl6ZVtjb25mdXNpb25fYnlfa2VlcF9zY29yZSRwaXBlbGluZV92YWx1ZT09LTAuNV0gPC0gIi0wLjUiCmNvbmZ1c2lvbl9ieV9rZWVwX3Njb3JlJGtlZXBfc2NvcmVfdmlzdWFsaXplW2NvbmZ1c2lvbl9ieV9rZWVwX3Njb3JlJHBpcGVsaW5lX3ZhbHVlPT0tMV0gPC0gIi0xIgpjb25mdXNpb25fYnlfa2VlcF9zY29yZSRrZWVwX3Njb3JlX3Zpc3VhbGl6ZVtjb25mdXNpb25fYnlfa2VlcF9zY29yZSRwaXBlbGluZV92YWx1ZTw9LTFdIDwtICI8IC0xIgoKY29uZnVzaW9uX2J5X2tlZXBfc2NvcmUka2VlcF9zY29yZV92aXN1YWxpemUgPC0gZmFjdG9yKGNvbmZ1c2lvbl9ieV9rZWVwX3Njb3JlJGtlZXBfc2NvcmVfdmlzdWFsaXplLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzPWMoIjwgLTEiLCAiLTEiLCAiLTAuNSIsICIwIiwgIjAuNSIsIjEiLCAiPiAxIikpCmBgYAoKYGBge3J9CmdncGxvdChjb25mdXNpb25fYnlfa2VlcF9zY29yZSwgYWVzKHg9Y29uZnVzaW9uX21hdHJpeCwgeT1uLAogICAgICAgICAgICAgICAgICAgZmlsbD1rZWVwX3Njb3JlX3Zpc3VhbGl6ZSwgY29sb3I9a2VlcF9zY29yZV92aXN1YWxpemUpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIHRoZW1lX2xpZ2h0KCkgKwogIGNvb3JkX2ZsaXAoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpCiAgKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWUgPSAnVmlyYWwgU2NvcmUnLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShjKHZpcmlkaXMoNikpLCAxKSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWUgPSAnVmlyYWwgU2NvcmUnLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShjKHZpcmlkaXMoNikpLCAwLjUpKSArCiAgeGxhYigiSW5kZXgiKSArCiAgeWxhYigiU2VxdWVuY2UgQ291bnQiKSArCiAgZmFjZXRfd3JhcCh+cGlwZWxpbmVfdHlwZSwgc2NhbGVzID0gImZyZWUiKQoKYGBgCg==